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 : &reserve_pub,
390 : &h,
391 : &got));
392 : }
393 1 : return 0;
394 : }
395 :
396 :
397 : /**
398 : * A reserve without enough money keeps it.
399 : *
400 : * @param pg the database context
401 : * @return 0 on success
402 : */
403 : static int
404 1 : check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg)
405 : {
406 : struct TALER_ReservePublicKeyP reserve_pub;
407 : struct TALER_EXCHANGEDB_Withdraw wd;
408 : struct WithdrawStatus st;
409 1 : struct TALER_Amount expect = TDB_amount ("1");
410 :
411 1 : TDB_account (pg,
412 : 10,
413 : &account);
414 1 : TDB_reserve_in (pg,
415 : &account,
416 : 10,
417 : "1",
418 : &reserve_pub);
419 1 : make_withdraw (2,
420 : "5",
421 : &reserve_pub,
422 : false,
423 : &wd);
424 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
425 : run_withdraw (pg,
426 : &wd,
427 : &st),
428 : free_withdraw (&wd));
429 1 : free_withdraw (&wd);
430 1 : FAILIF (st.balance_ok);
431 1 : FAILIF (! st.age_ok);
432 1 : FAILIF (st.idempotent);
433 1 : FAILIF (0 != TALER_amount_cmp (&st.reserve_balance,
434 : &expect));
435 1 : FAILIF (0 != TDB_count (pg,
436 : "FROM withdraw"));
437 1 : return 0;
438 : }
439 :
440 :
441 : /**
442 : * A funded reserve is drained and the withdraw is recorded.
443 : *
444 : * @param pg the database context
445 : * @return 0 on success
446 : */
447 : static int
448 1 : check_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg)
449 : {
450 : struct TALER_ReservePublicKeyP reserve_pub;
451 : struct TALER_EXCHANGEDB_Withdraw wd;
452 : struct TALER_EXCHANGEDB_Withdraw got;
453 : struct TALER_EXCHANGEDB_Reserve reserve;
454 : struct WithdrawStatus st;
455 1 : struct TALER_Amount expect_balance = TDB_amount ("5");
456 1 : struct TALER_Amount expect_left = TDB_amount ("5");
457 :
458 1 : TDB_reserve_in (pg,
459 : &account,
460 : 11,
461 : "10",
462 : &reserve_pub);
463 1 : make_withdraw (11,
464 : "5",
465 : &reserve_pub,
466 : true,
467 : &wd);
468 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
469 : run_withdraw (pg,
470 : &wd,
471 : &st),
472 : free_withdraw (&wd));
473 1 : FAILIF_C (! st.balance_ok,
474 : free_withdraw (&wd));
475 1 : FAILIF_C (! st.age_ok,
476 : free_withdraw (&wd));
477 1 : FAILIF_C (st.idempotent,
478 : free_withdraw (&wd));
479 1 : FAILIF_C (st.nonce_reuse,
480 : free_withdraw (&wd));
481 1 : FAILIF_C (1 != TDB_count (pg,
482 : "FROM withdraw"),
483 : free_withdraw (&wd));
484 :
485 : /* the reserve was debited */
486 1 : memset (&reserve,
487 : 0,
488 : sizeof (reserve));
489 1 : reserve.pub = reserve_pub;
490 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
491 : TALER_EXCHANGEDB_get_reserve (pg,
492 : &reserve),
493 : free_withdraw (&wd));
494 1 : FAILIF_C (0 != TALER_amount_cmp (&reserve.balance,
495 : &expect_left),
496 : free_withdraw (&wd));
497 :
498 : /* the request can be looked up by its reserve and planchet hash */
499 1 : memset (&got,
500 : 0,
501 : sizeof (got));
502 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
503 : TALER_EXCHANGEDB_get_withdraw (pg,
504 : &reserve_pub,
505 : &wd.planchets_h,
506 : &got),
507 : free_withdraw (&wd));
508 1 : FAILIF_C (0 != TALER_amount_cmp (&got.amount_with_fee,
509 : &expect_balance),
510 : free_withdraw (&got); free_withdraw (&wd));
511 : /* ... and names its reserve and its row */
512 1 : FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
513 : &reserve_pub),
514 : free_withdraw (&got); free_withdraw (&wd));
515 1 : FAILIF_C (0 == got.withdraw_id,
516 : free_withdraw (&got); free_withdraw (&wd));
517 1 : FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
518 : &reserve_pub),
519 : free_withdraw (&got); free_withdraw (&wd));
520 1 : FAILIF_C (0 != GNUNET_memcmp (&got.reserve_sig,
521 : &wd.reserve_sig),
522 : free_withdraw (&got); free_withdraw (&wd));
523 1 : FAILIF_C (1 != got.num_coins,
524 : free_withdraw (&got); free_withdraw (&wd));
525 1 : FAILIF_C (denom.serial != got.denom_serials[0],
526 : free_withdraw (&got); free_withdraw (&wd));
527 1 : FAILIF_C (got.no_blinding_seed,
528 : free_withdraw (&got); free_withdraw (&wd));
529 1 : free_withdraw (&got);
530 :
531 : /* the planchet hash is only unique per reserve: another reserve does
532 : not see this request */
533 : {
534 : struct TALER_ReservePublicKeyP other_pub;
535 :
536 1 : TDB_FILL (other_pub,
537 : 99);
538 1 : memset (&got,
539 : 0,
540 : sizeof (got));
541 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
542 : TALER_EXCHANGEDB_get_withdraw (pg,
543 : &other_pub,
544 : &wd.planchets_h,
545 : &got),
546 : free_withdraw (&wd));
547 : }
548 :
549 : /* a replay of the same request is idempotent and does not debit again */
550 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
551 : run_withdraw (pg,
552 : &wd,
553 : &st),
554 : free_withdraw (&wd));
555 1 : FAILIF_C (! st.idempotent,
556 : free_withdraw (&wd));
557 1 : FAILIF_C (1 != TDB_count (pg,
558 : "FROM withdraw"),
559 : free_withdraw (&wd));
560 1 : memset (&reserve,
561 : 0,
562 : sizeof (reserve));
563 1 : reserve.pub = reserve_pub;
564 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
565 : TALER_EXCHANGEDB_get_reserve (pg,
566 : &reserve),
567 : free_withdraw (&wd));
568 1 : FAILIF_C (0 != TALER_amount_cmp (&reserve.balance,
569 : &expect_left),
570 : free_withdraw (&wd));
571 1 : free_withdraw (&wd);
572 1 : return 0;
573 : }
574 :
575 :
576 : /**
577 : * Reusing a blinding seed for a different withdraw is refused, and the
578 : * reserve is left debited -- the caller has to roll back.
579 : *
580 : * @param pg the database context
581 : * @return 0 on success
582 : */
583 : static int
584 1 : check_nonce_reuse (struct TALER_EXCHANGEDB_PostgresContext *pg)
585 : {
586 : struct TALER_ReservePublicKeyP reserve_pub;
587 : struct TALER_EXCHANGEDB_Withdraw wd;
588 : struct WithdrawStatus st;
589 :
590 1 : TDB_reserve_in (pg,
591 : &account,
592 : 12,
593 : "10",
594 : &reserve_pub);
595 : /* a fresh planchet hash, but the blinding seed of check_withdraw() */
596 1 : make_withdraw (13,
597 : "5",
598 : &reserve_pub,
599 : true,
600 : &wd);
601 1 : TDB_fill (&wd.blinding_seed,
602 : sizeof (wd.blinding_seed),
603 : 11);
604 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
605 : run_withdraw (pg,
606 : &wd,
607 : &st),
608 : free_withdraw (&wd));
609 1 : FAILIF_C (! st.nonce_reuse,
610 : free_withdraw (&wd));
611 1 : FAILIF_C (st.idempotent,
612 : free_withdraw (&wd));
613 1 : free_withdraw (&wd);
614 : /* No withdraw row was written... */
615 1 : FAILIF (1 != TDB_count (pg,
616 : "FROM withdraw"));
617 : /* ...but the reserve was debited before the seed was checked, so the
618 : caller has to roll the transaction back. This is what the check is
619 : really about: the function is not safe to call outside a transaction. */
620 : {
621 : struct TALER_EXCHANGEDB_Reserve reserve;
622 1 : struct TALER_Amount debited = TDB_amount ("5");
623 :
624 1 : memset (&reserve,
625 : 0,
626 : sizeof (reserve));
627 1 : reserve.pub = reserve_pub;
628 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
629 : TALER_EXCHANGEDB_get_reserve (pg,
630 : &reserve));
631 1 : FAILIF (0 != TALER_amount_cmp (&reserve.balance,
632 : &debited));
633 : }
634 1 : return 0;
635 : }
636 :
637 :
638 : /**
639 : * A reserve with a birthday refuses a withdraw that commits to too high
640 : * an age.
641 : *
642 : * @param pg the database context
643 : * @return 0 on success
644 : */
645 : static int
646 1 : check_age_restriction (struct TALER_EXCHANGEDB_PostgresContext *pg)
647 : {
648 : struct TALER_ReservePublicKeyP reserve_pub;
649 : struct TALER_EXCHANGEDB_Withdraw wd;
650 : struct WithdrawStatus st;
651 : char *hex;
652 :
653 1 : TDB_reserve_in (pg,
654 : &account,
655 : 14,
656 : "10",
657 : &reserve_pub);
658 : /* born 20000 days after the epoch, i.e. in 2024 */
659 1 : hex = TDB_hex (&reserve_pub,
660 : sizeof (reserve_pub));
661 1 : FAILIF_C (GNUNET_OK !=
662 : TDB_exec (pg,
663 : "UPDATE reserves"
664 : " SET birthday=20000"
665 : " WHERE reserve_pub=decode('%s','hex');",
666 : hex),
667 : GNUNET_free (hex));
668 1 : GNUNET_free (hex);
669 :
670 1 : make_withdraw (14,
671 : "5",
672 : &reserve_pub,
673 : false,
674 : &wd);
675 : /* no age commitment at all, from a reserve that has a birthday */
676 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
677 : run_withdraw (pg,
678 : &wd,
679 : &st),
680 : free_withdraw (&wd));
681 1 : free_withdraw (&wd);
682 1 : FAILIF (st.age_ok);
683 1 : FAILIF (20000 != st.reserve_birthday);
684 1 : FAILIF (1 != TDB_count (pg,
685 : "FROM withdraw"));
686 1 : return 0;
687 : }
688 :
689 :
690 : /**
691 : * The iterators see the withdraw, and the KYC view attributes it to the
692 : * account that funded the reserve.
693 : *
694 : * @param pg the database context
695 : * @return 0 on success
696 : */
697 : static int
698 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
699 : {
700 : struct TALER_HashBlindedPlanchetsP h;
701 : struct WithdrawContext ctx;
702 : struct AmountContext actx;
703 :
704 1 : TDB_FILL (h,
705 : 11);
706 1 : memset (&ctx,
707 : 0,
708 : sizeof (ctx));
709 1 : ctx.planchets_h = &h;
710 1 : FAILIF (1 !=
711 : TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
712 : 0,
713 : &withdraw_cb,
714 : &ctx));
715 1 : FAILIF (1 != ctx.matched);
716 1 : FAILIF (1 != ctx.num_denom_serials);
717 1 : FAILIF (denom.serial != ctx.denom_serial);
718 1 : FAILIF (! ctx.have_seed);
719 :
720 1 : memset (&ctx,
721 : 0,
722 : sizeof (ctx));
723 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
724 : TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
725 : 1000,
726 : &withdraw_cb,
727 : &ctx));
728 1 : FAILIF (0 != ctx.total);
729 :
730 1 : memset (&ctx,
731 : 0,
732 : sizeof (ctx));
733 1 : ctx.stop_after = 1;
734 1 : FAILIF (1 !=
735 : TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
736 : 0,
737 : &withdraw_cb,
738 : &ctx));
739 1 : FAILIF (1 != ctx.total);
740 :
741 : /* the KYC view groups by the account that funded the reserve */
742 1 : memset (&actx,
743 : 0,
744 : sizeof (actx));
745 1 : actx.ret = GNUNET_OK;
746 1 : FAILIF (1 !=
747 : TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check (
748 : pg,
749 : &account.h_normalized,
750 : GNUNET_TIME_UNIT_ZERO_ABS,
751 : &amount_cb,
752 : &actx));
753 1 : FAILIF (5 != actx.value_sum);
754 :
755 : /* an account nobody withdrew against has nothing */
756 : {
757 : struct TALER_NormalizedPaytoHashP other;
758 :
759 1 : TDB_FILL (other,
760 : 77);
761 1 : memset (&actx,
762 : 0,
763 : sizeof (actx));
764 1 : actx.ret = GNUNET_OK;
765 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
766 : TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check (
767 : pg,
768 : &other,
769 : GNUNET_TIME_UNIT_ZERO_ABS,
770 : &amount_cb,
771 : &actx));
772 1 : FAILIF (0 != actx.total);
773 : }
774 :
775 : /* a time limit in the future hides everything */
776 1 : memset (&actx,
777 : 0,
778 : sizeof (actx));
779 1 : actx.ret = GNUNET_OK;
780 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
781 : TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check (
782 : pg,
783 : &account.h_normalized,
784 : GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS),
785 : &amount_cb,
786 : &actx));
787 1 : FAILIF (0 != actx.total);
788 1 : return 0;
789 : }
790 :
791 :
792 : /**
793 : * The checks to run, in order.
794 : */
795 : static const struct TDB_Test tests[] = {
796 : { "withdraw-unknown-reserve",
797 : &check_unknown_reserve },
798 : { "withdraw-insufficient-balance",
799 : &check_insufficient_balance },
800 : { "withdraw-withdraw",
801 : &check_withdraw },
802 : { "withdraw-nonce-reuse",
803 : &check_nonce_reuse },
804 : { "withdraw-age-restriction",
805 : &check_age_restriction },
806 : { "withdraw-iterate",
807 : &check_iterate },
808 : { NULL, NULL }
809 : };
810 :
811 :
812 : int
813 1 : main (int argc,
814 : char *const *argv)
815 : {
816 : int ret;
817 :
818 1 : ret = TDB_main (argc,
819 : argv,
820 : "test-withdraw",
821 : "Tests for the exchangedb `withdraw' table",
822 : tests);
823 1 : TDB_account_free (&account);
824 1 : TDB_denom_free (&denom);
825 1 : return ret;
826 : }
827 :
828 :
829 : /* end of test_withdraw.c */
|