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_recoup.c
18 : * @brief tests for the exchangedb functions whose primary table is `recoup`
19 : * @author Christian Grothoff
20 : *
21 : * Covers #TALER_EXCHANGEDB_do_recoup() and
22 : * #TALER_EXCHANGEDB_iterate_recoups_above_serial_id().
23 : *
24 : * `recoup` references `known_coins` and `withdraw`, so a full chain is
25 : * built for each check: a funded reserve, a withdraw from it and a coin.
26 : * do_recoup() moves the coin's whole remaining balance back to the reserve,
27 : * which is why the checks look at both sides afterwards.
28 : */
29 : #include "test_common.h"
30 : #include "exchange-database/do_recoup.h"
31 : #include "exchange-database/get_reserve.h"
32 : #include "exchange-database/iterate_recoups_above_serial_id.h"
33 : #include "exchange-database/iterate_records_by_table.h"
34 : #include "exchange-database/insert_records_by_table.h"
35 :
36 :
37 : /**
38 : * Account the checks fund their reserves from.
39 : */
40 : static struct TDB_Account account;
41 :
42 :
43 : /**
44 : * Denomination the checks use.
45 : */
46 : static struct TDB_Denom denom;
47 :
48 :
49 : /**
50 : * Closure for #recoup_cb().
51 : */
52 : struct RecoupContext
53 : {
54 : /**
55 : * How many rows did the callback see?
56 : */
57 : unsigned int total;
58 :
59 : /**
60 : * Stop after this many rows; 0 for no limit.
61 : */
62 : unsigned int stop_after;
63 :
64 : /**
65 : * Coin we are looking for, NULL to match nothing.
66 : */
67 : const struct TALER_CoinSpendPublicKeyP *coin_pub;
68 :
69 : /**
70 : * How many times did we see it?
71 : */
72 : unsigned int matched;
73 :
74 : /**
75 : * Amount reported for it.
76 : */
77 : struct TALER_Amount amount;
78 :
79 : /**
80 : * Reserve reported for it.
81 : */
82 : struct TALER_ReservePublicKeyP reserve_pub;
83 :
84 : /**
85 : * Blinding secret reported for it.
86 : */
87 : union GNUNET_CRYPTO_BlindingSecretP coin_blind;
88 : };
89 :
90 :
91 : /**
92 : * Callback for #TALER_EXCHANGEDB_iterate_recoups_above_serial_id().
93 : *
94 : * @param cls a `struct RecoupContext *`
95 : * @param rowid row of the recoup
96 : * @param timestamp when the recoup was requested
97 : * @param amount how much went back to the reserve
98 : * @param reserve_pub the reserve that was credited
99 : * @param coin the coin that was recouped
100 : * @param denom_pub denomination of @a coin
101 : * @param coin_sig signature over the request
102 : * @param coin_blind blinding secret of the coin
103 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
104 : */
105 : static enum GNUNET_GenericReturnValue
106 3 : recoup_cb (void *cls,
107 : uint64_t rowid,
108 : struct GNUNET_TIME_Timestamp timestamp,
109 : const struct TALER_Amount *amount,
110 : const struct TALER_ReservePublicKeyP *reserve_pub,
111 : const struct TALER_CoinPublicInfo *coin,
112 : const struct TALER_DenominationPublicKey *denom_pub,
113 : const struct TALER_CoinSpendSignatureP *coin_sig,
114 : const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
115 : {
116 3 : struct RecoupContext *ctx = cls;
117 :
118 : (void) rowid;
119 : (void) timestamp;
120 : (void) denom_pub;
121 : (void) coin_sig;
122 3 : ctx->total++;
123 3 : if ( (NULL != ctx->coin_pub) &&
124 1 : (0 == GNUNET_memcmp (&coin->coin_pub,
125 : ctx->coin_pub)) )
126 : {
127 1 : ctx->matched++;
128 1 : ctx->amount = *amount;
129 1 : ctx->reserve_pub = *reserve_pub;
130 1 : ctx->coin_blind = *coin_blind;
131 : }
132 3 : if ( (0 != ctx->stop_after) &&
133 1 : (ctx->total >= ctx->stop_after) )
134 1 : return GNUNET_SYSERR;
135 2 : return GNUNET_OK;
136 : }
137 :
138 :
139 : /**
140 : * Outcome of a recoup request.
141 : */
142 : struct RecoupStatus
143 : {
144 : /**
145 : * Was the recoup accepted?
146 : */
147 : bool recoup_ok;
148 :
149 : /**
150 : * Did something go wrong inside the database?
151 : */
152 : bool internal_failure;
153 :
154 : /**
155 : * When the recoup happened.
156 : */
157 : struct GNUNET_TIME_Timestamp recoup_timestamp;
158 :
159 : /**
160 : * Amount the recoup credited.
161 : */
162 : struct TALER_Amount recoup_amount;
163 : };
164 :
165 :
166 : /**
167 : * Recoup a coin into a reserve.
168 : *
169 : * @param pg the database context
170 : * @param reserve_pub reserve to credit
171 : * @param withdraw_id withdraw that justifies the recoup
172 : * @param coin_pub coin to recoup
173 : * @param known_coin_id row of @a coin_pub
174 : * @param seed seed for the blinding secret and coin signature
175 : * @param[out] st set to the outcome
176 : * @return transaction status
177 : */
178 : static enum GNUNET_DB_QueryStatus
179 8 : run_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg,
180 : const struct TALER_ReservePublicKeyP *reserve_pub,
181 : uint64_t withdraw_id,
182 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
183 : uint64_t known_coin_id,
184 : uint32_t seed,
185 : struct RecoupStatus *st)
186 : {
187 : union GNUNET_CRYPTO_BlindingSecretP coin_bks;
188 : struct TALER_CoinSpendSignatureP coin_sig;
189 :
190 8 : TDB_fill (&coin_bks,
191 : sizeof (coin_bks),
192 : seed);
193 8 : TDB_fill (&coin_sig,
194 : sizeof (coin_sig),
195 : seed);
196 8 : memset (st,
197 : 0,
198 : sizeof (*st));
199 8 : st->recoup_timestamp = GNUNET_TIME_timestamp_get ();
200 8 : return TALER_EXCHANGEDB_do_recoup (pg,
201 : reserve_pub,
202 : withdraw_id,
203 : 0,
204 : &coin_bks,
205 : coin_pub,
206 : known_coin_id,
207 : &coin_sig,
208 : &st->recoup_timestamp,
209 : &st->recoup_amount,
210 : &st->recoup_ok,
211 : &st->internal_failure);
212 : }
213 :
214 :
215 : /**
216 : * Recouping a coin the exchange does not know is an internal failure.
217 : *
218 : * @param pg the database context
219 : * @return 0 on success
220 : */
221 : static int
222 1 : check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
223 : {
224 : struct TALER_ReservePublicKeyP reserve_pub;
225 : struct TALER_CoinSpendPublicKeyP coin_pub;
226 1 : struct RecoupContext ctx = { 0 };
227 : struct RecoupStatus st;
228 : uint64_t withdraw_id;
229 :
230 1 : TDB_denom (pg,
231 : 10,
232 : "5",
233 : "0.1",
234 : &denom);
235 1 : TDB_account (pg,
236 : 10,
237 : &account);
238 1 : TDB_reserve_in (pg,
239 : &account,
240 : 10,
241 : "10",
242 : &reserve_pub);
243 1 : withdraw_id = TDB_withdraw (pg,
244 : &denom,
245 : &reserve_pub,
246 : 10,
247 : "5");
248 1 : TDB_FILL (coin_pub,
249 : 99);
250 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
251 : run_recoup (pg,
252 : &reserve_pub,
253 : withdraw_id,
254 : &coin_pub,
255 : 1,
256 : 99,
257 : &st));
258 1 : FAILIF (! st.internal_failure);
259 1 : FAILIF (st.recoup_ok);
260 1 : FAILIF (0 != TDB_count (pg,
261 : "FROM recoup"));
262 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
263 : TALER_EXCHANGEDB_iterate_recoups_above_serial_id (pg,
264 : 0,
265 : &recoup_cb,
266 : &ctx));
267 1 : FAILIF (0 != ctx.total);
268 1 : return 0;
269 : }
270 :
271 :
272 : /**
273 : * A coin that is already empty and was never recouped is refused.
274 : *
275 : * @param pg the database context
276 : * @return 0 on success
277 : */
278 : static int
279 1 : check_empty_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
280 : {
281 : struct TALER_ReservePublicKeyP reserve_pub;
282 : struct TALER_CoinPublicInfo coin;
283 : struct RecoupStatus st;
284 : uint64_t known_coin_id;
285 : uint64_t withdraw_id;
286 : char *hex;
287 :
288 1 : TDB_reserve_in (pg,
289 : &account,
290 : 11,
291 : "10",
292 : &reserve_pub);
293 1 : withdraw_id = TDB_withdraw (pg,
294 : &denom,
295 : &reserve_pub,
296 : 11,
297 : "5");
298 1 : TDB_coin (pg,
299 : &denom,
300 : 20,
301 : &coin,
302 : &known_coin_id);
303 1 : hex = TDB_hex (&coin.coin_pub,
304 : sizeof (coin.coin_pub));
305 1 : FAILIF_C (GNUNET_OK !=
306 : TDB_exec (pg,
307 : "UPDATE known_coins"
308 : " SET remaining=ROW(0,0)::taler_amount"
309 : " WHERE coin_pub=decode('%s','hex');",
310 : hex),
311 : GNUNET_free (hex); TDB_coin_free (&coin));
312 1 : GNUNET_free (hex);
313 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
314 : run_recoup (pg,
315 : &reserve_pub,
316 : withdraw_id,
317 : &coin.coin_pub,
318 : known_coin_id,
319 : 20,
320 : &st),
321 : TDB_coin_free (&coin));
322 1 : TDB_coin_free (&coin);
323 1 : FAILIF (st.internal_failure);
324 : /* nothing to give back, and no earlier recoup to report */
325 1 : FAILIF (st.recoup_ok);
326 1 : FAILIF (0 != TDB_count (pg,
327 : "FROM recoup"));
328 1 : return 0;
329 : }
330 :
331 :
332 : /**
333 : * A funded coin is drained and the reserve is credited.
334 : *
335 : * @param pg the database context
336 : * @return 0 on success
337 : */
338 : static int
339 1 : check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg)
340 : {
341 : struct TALER_ReservePublicKeyP reserve_pub;
342 : struct TALER_CoinPublicInfo coin;
343 : struct TALER_EXCHANGEDB_Reserve reserve;
344 : struct RecoupStatus st;
345 : struct RecoupContext ctx;
346 1 : struct TALER_Amount expect_coin = TDB_amount ("5");
347 1 : struct TALER_Amount expect_reserve = TDB_amount ("10");
348 : union GNUNET_CRYPTO_BlindingSecretP expect_bks;
349 : uint64_t known_coin_id;
350 : uint64_t withdraw_id;
351 : char *hex;
352 :
353 1 : TDB_reserve_in (pg,
354 : &account,
355 : 12,
356 : "10",
357 : &reserve_pub);
358 1 : withdraw_id = TDB_withdraw (pg,
359 : &denom,
360 : &reserve_pub,
361 : 12,
362 : "5");
363 1 : TDB_coin (pg,
364 : &denom,
365 : 21,
366 : &coin,
367 : &known_coin_id);
368 : /* EUR:10 came in and EUR:5 was withdrawn, so EUR:5 is left */
369 1 : memset (&reserve,
370 : 0,
371 : sizeof (reserve));
372 1 : reserve.pub = reserve_pub;
373 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
374 : TALER_EXCHANGEDB_get_reserve (pg,
375 : &reserve),
376 : TDB_coin_free (&coin));
377 :
378 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
379 : run_recoup (pg,
380 : &reserve_pub,
381 : withdraw_id,
382 : &coin.coin_pub,
383 : known_coin_id,
384 : 21,
385 : &st),
386 : TDB_coin_free (&coin));
387 1 : FAILIF_C (st.internal_failure,
388 : TDB_coin_free (&coin));
389 1 : FAILIF_C (! st.recoup_ok,
390 : TDB_coin_free (&coin));
391 1 : FAILIF_C (1 != TDB_count (pg,
392 : "FROM recoup"),
393 : TDB_coin_free (&coin));
394 :
395 : /* the coin is empty... */
396 1 : hex = TDB_hex (&coin.coin_pub,
397 : sizeof (coin.coin_pub));
398 1 : FAILIF_C (1 != TDB_count (pg,
399 : "FROM known_coins"
400 : " WHERE coin_pub=decode('%s','hex')"
401 : " AND remaining=ROW(0,0)::taler_amount",
402 : hex),
403 : GNUNET_free (hex); TDB_coin_free (&coin));
404 1 : GNUNET_free (hex);
405 : /* ...and the reserve got the money */
406 1 : memset (&reserve,
407 : 0,
408 : sizeof (reserve));
409 1 : reserve.pub = reserve_pub;
410 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
411 : TALER_EXCHANGEDB_get_reserve (pg,
412 : &reserve),
413 : TDB_coin_free (&coin));
414 1 : FAILIF_C (0 != TALER_amount_cmp (&reserve.balance,
415 : &expect_reserve),
416 : TDB_coin_free (&coin));
417 :
418 : /* the recoup is reported with everything it was made with */
419 1 : TDB_FILL (expect_bks,
420 : 21);
421 1 : memset (&ctx,
422 : 0,
423 : sizeof (ctx));
424 1 : ctx.coin_pub = &coin.coin_pub;
425 1 : FAILIF_C (0 >=
426 : TALER_EXCHANGEDB_iterate_recoups_above_serial_id (pg,
427 : 0,
428 : &recoup_cb,
429 : &ctx),
430 : TDB_coin_free (&coin));
431 1 : FAILIF_C (1 != ctx.matched,
432 : TDB_coin_free (&coin));
433 1 : FAILIF_C (0 != TALER_amount_cmp (&ctx.amount,
434 : &expect_coin),
435 : TDB_coin_free (&coin));
436 1 : FAILIF_C (0 != GNUNET_memcmp (&ctx.reserve_pub,
437 : &reserve_pub),
438 : TDB_coin_free (&coin));
439 1 : FAILIF_C (0 != GNUNET_memcmp (&ctx.coin_blind,
440 : &expect_bks),
441 : TDB_coin_free (&coin));
442 :
443 : /* recouping the same coin again finds the earlier recoup */
444 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
445 : run_recoup (pg,
446 : &reserve_pub,
447 : withdraw_id,
448 : &coin.coin_pub,
449 : known_coin_id,
450 : 21,
451 : &st),
452 : TDB_coin_free (&coin));
453 1 : FAILIF_C (! st.recoup_ok,
454 : TDB_coin_free (&coin));
455 1 : FAILIF_C (st.internal_failure,
456 : TDB_coin_free (&coin));
457 1 : FAILIF_C (1 != TDB_count (pg,
458 : "FROM recoup"),
459 : TDB_coin_free (&coin));
460 :
461 : /* ...but a recoup of the same coin for another withdraw operation
462 : is not the same recoup: the coin has nothing left for it */
463 : {
464 : uint64_t other_withdraw_id;
465 :
466 1 : other_withdraw_id = TDB_withdraw (pg,
467 : &denom,
468 : &reserve_pub,
469 : 13,
470 : "5");
471 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
472 : run_recoup (pg,
473 : &reserve_pub,
474 : other_withdraw_id,
475 : &coin.coin_pub,
476 : known_coin_id,
477 : 21,
478 : &st),
479 : TDB_coin_free (&coin));
480 1 : FAILIF_C (st.recoup_ok,
481 : TDB_coin_free (&coin));
482 1 : FAILIF_C (st.internal_failure,
483 : TDB_coin_free (&coin));
484 1 : FAILIF_C (1 != TDB_count (pg,
485 : "FROM recoup"),
486 : TDB_coin_free (&coin));
487 : }
488 1 : TDB_coin_free (&coin);
489 1 : return 0;
490 : }
491 :
492 :
493 : /**
494 : * Closure for #replication_cb().
495 : */
496 : struct ReplicationContext
497 : {
498 : /**
499 : * Coin index every record must carry.
500 : */
501 : uint32_t expected_coin_index;
502 :
503 : /**
504 : * Records seen.
505 : */
506 : unsigned int seen;
507 :
508 : /**
509 : * Set if a record carried another coin index.
510 : */
511 : bool mismatch;
512 : };
513 :
514 :
515 : /**
516 : * Counts the replication records of the recoup table and checks
517 : * their coin index.
518 : *
519 : * @param cls a `struct ReplicationContext *`
520 : * @param td the record
521 : * @return #GNUNET_OK to continue
522 : */
523 : static int
524 2 : replication_cb (void *cls,
525 : const struct TALER_EXCHANGEDB_TableData *td)
526 : {
527 2 : struct ReplicationContext *rc = cls;
528 :
529 2 : if (TALER_EXCHANGEDB_RT_RECOUP != td->table)
530 : {
531 0 : rc->mismatch = true;
532 0 : return GNUNET_OK;
533 : }
534 2 : rc->seen++;
535 2 : if (rc->expected_coin_index != td->details.recoup.coin_index)
536 0 : rc->mismatch = true;
537 2 : return GNUNET_OK;
538 : }
539 :
540 :
541 : /**
542 : * Replication carries the coin index of a recoup.
543 : *
544 : * @param pg the database context
545 : * @return 0 on success
546 : */
547 : static int
548 1 : check_replication (struct TALER_EXCHANGEDB_PostgresContext *pg)
549 : {
550 1 : struct ReplicationContext rc = {
551 : .expected_coin_index = 0
552 : };
553 2 : struct TALER_EXCHANGEDB_TableData td = {
554 : .table = TALER_EXCHANGEDB_RT_RECOUP,
555 : .serial = 77,
556 : .details.recoup.coin_index = 7,
557 1 : .details.recoup.amount = TDB_amount ("5"),
558 1 : .details.recoup.timestamp = GNUNET_TIME_timestamp_get ()
559 : };
560 :
561 : /* the row made by check_recoup() is reported with its index 0 */
562 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
563 : TALER_EXCHANGEDB_iterate_records_by_table (pg,
564 : TALER_EXCHANGEDB_RT_RECOUP,
565 : 0,
566 : &replication_cb,
567 : &rc));
568 1 : FAILIF (1 != rc.seen);
569 : /* a replicated row keeps its index */
570 : {
571 : struct TALER_CoinPublicInfo coin;
572 : uint64_t known_coin_id;
573 :
574 1 : TDB_coin (pg,
575 : &denom,
576 : 21,
577 : &coin,
578 : &known_coin_id);
579 1 : td.details.recoup.coin_pub = coin.coin_pub;
580 1 : TDB_coin_free (&coin);
581 : }
582 : {
583 : struct TALER_ReservePublicKeyP reserve_pub;
584 :
585 1 : TDB_reserve_in (pg,
586 : &account,
587 : 14,
588 : "10",
589 : &reserve_pub);
590 1 : td.details.recoup.withdraw_serial_id = TDB_withdraw (pg,
591 : &denom,
592 : &reserve_pub,
593 : 14,
594 : "5");
595 : }
596 1 : TDB_FILL (td.details.recoup.coin_sig,
597 : 77);
598 1 : TDB_FILL (td.details.recoup.coin_blind,
599 : 77);
600 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
601 : TALER_EXCHANGEDB_insert_records_by_table (pg,
602 : &td));
603 1 : FAILIF (1 != TDB_count (pg,
604 : "FROM recoup WHERE coin_index=7"));
605 1 : rc.seen = 0;
606 1 : rc.expected_coin_index = 7;
607 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
608 : TALER_EXCHANGEDB_iterate_records_by_table (pg,
609 : TALER_EXCHANGEDB_RT_RECOUP,
610 : 76,
611 : &replication_cb,
612 : &rc));
613 1 : FAILIF (1 != rc.seen);
614 1 : FAILIF (rc.mismatch);
615 1 : return 0;
616 : }
617 :
618 :
619 : /**
620 : * The iterator's serial bound and abort return behave as documented.
621 : *
622 : * @param pg the database context
623 : * @return 0 on success
624 : */
625 : static int
626 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
627 : {
628 : struct RecoupContext ctx;
629 :
630 1 : memset (&ctx,
631 : 0,
632 : sizeof (ctx));
633 1 : FAILIF (1 !=
634 : TALER_EXCHANGEDB_iterate_recoups_above_serial_id (pg,
635 : 0,
636 : &recoup_cb,
637 : &ctx));
638 1 : memset (&ctx,
639 : 0,
640 : sizeof (ctx));
641 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
642 : TALER_EXCHANGEDB_iterate_recoups_above_serial_id (pg,
643 : 1000,
644 : &recoup_cb,
645 : &ctx));
646 1 : FAILIF (0 != ctx.total);
647 1 : memset (&ctx,
648 : 0,
649 : sizeof (ctx));
650 1 : ctx.stop_after = 1;
651 1 : FAILIF (1 !=
652 : TALER_EXCHANGEDB_iterate_recoups_above_serial_id (pg,
653 : 0,
654 : &recoup_cb,
655 : &ctx));
656 1 : FAILIF (1 != ctx.total);
657 1 : return 0;
658 : }
659 :
660 :
661 : /**
662 : * A recouped coin that was credited again (a refund does that) can be
663 : * recouped a second time. Replaying the request afterwards must report
664 : * the latest recoup, not an arbitrary earlier one.
665 : *
666 : * @param pg the database context
667 : * @return 0 on success
668 : */
669 : static int
670 1 : check_replay_latest (struct TALER_EXCHANGEDB_PostgresContext *pg)
671 : {
672 : struct TALER_ReservePublicKeyP reserve_pub;
673 : struct TALER_CoinPublicInfo coin;
674 : struct TALER_EXCHANGEDB_Reserve reserve;
675 : struct RecoupStatus st;
676 1 : struct TALER_Amount expect_first = TDB_amount ("5");
677 1 : struct TALER_Amount expect_second = TDB_amount ("2");
678 1 : struct TALER_Amount expect_reserve = TDB_amount ("12");
679 : uint64_t known_coin_id;
680 : uint64_t withdraw_id;
681 : char *hex;
682 :
683 1 : TDB_reserve_in (pg,
684 : &account,
685 : 15,
686 : "10",
687 : &reserve_pub);
688 1 : withdraw_id = TDB_withdraw (pg,
689 : &denom,
690 : &reserve_pub,
691 : 15,
692 : "5");
693 1 : TDB_coin (pg,
694 : &denom,
695 : 22,
696 : &coin,
697 : &known_coin_id);
698 1 : hex = TDB_hex (&coin.coin_pub,
699 : sizeof (coin.coin_pub));
700 :
701 : /* first recoup drains the coin's EUR:5 */
702 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
703 : run_recoup (pg,
704 : &reserve_pub,
705 : withdraw_id,
706 : &coin.coin_pub,
707 : known_coin_id,
708 : 22,
709 : &st),
710 : GNUNET_free (hex); TDB_coin_free (&coin));
711 1 : FAILIF_C ( (! st.recoup_ok) ||
712 : (st.internal_failure) ||
713 : (0 != TALER_amount_cmp (&st.recoup_amount,
714 : &expect_first)),
715 : GNUNET_free (hex); TDB_coin_free (&coin));
716 :
717 : /* the coin gets EUR:2 back, as a refund would do */
718 1 : FAILIF_C (GNUNET_OK !=
719 : TDB_exec (pg,
720 : "UPDATE known_coins"
721 : " SET remaining=ROW(2,0)::taler_amount"
722 : " WHERE coin_pub=decode('%s','hex');",
723 : hex),
724 : GNUNET_free (hex); TDB_coin_free (&coin));
725 :
726 : /* second recoup for the same withdraw drains the EUR:2 */
727 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
728 : run_recoup (pg,
729 : &reserve_pub,
730 : withdraw_id,
731 : &coin.coin_pub,
732 : known_coin_id,
733 : 22,
734 : &st),
735 : GNUNET_free (hex); TDB_coin_free (&coin));
736 1 : FAILIF_C ( (! st.recoup_ok) ||
737 : (st.internal_failure) ||
738 : (0 != TALER_amount_cmp (&st.recoup_amount,
739 : &expect_second)),
740 : GNUNET_free (hex); TDB_coin_free (&coin));
741 1 : FAILIF_C (2 != TDB_count (pg,
742 : "FROM recoup"
743 : " WHERE coin_pub=decode('%s','hex')",
744 : hex),
745 : GNUNET_free (hex); TDB_coin_free (&coin));
746 :
747 : /* replaying the request reports the latest recoup... */
748 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
749 : run_recoup (pg,
750 : &reserve_pub,
751 : withdraw_id,
752 : &coin.coin_pub,
753 : known_coin_id,
754 : 22,
755 : &st),
756 : GNUNET_free (hex); TDB_coin_free (&coin));
757 1 : FAILIF_C ( (! st.recoup_ok) ||
758 : (st.internal_failure),
759 : GNUNET_free (hex); TDB_coin_free (&coin));
760 1 : FAILIF_C (0 != TALER_amount_cmp (&st.recoup_amount,
761 : &expect_second),
762 : GNUNET_free (hex); TDB_coin_free (&coin));
763 : /* ...and does not touch the tables again */
764 1 : FAILIF_C (2 != TDB_count (pg,
765 : "FROM recoup"
766 : " WHERE coin_pub=decode('%s','hex')",
767 : hex),
768 : GNUNET_free (hex); TDB_coin_free (&coin));
769 1 : GNUNET_free (hex);
770 1 : TDB_coin_free (&coin);
771 : /* EUR:10 in, EUR:5 withdrawn, EUR:5 and EUR:2 recouped */
772 1 : memset (&reserve,
773 : 0,
774 : sizeof (reserve));
775 1 : reserve.pub = reserve_pub;
776 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
777 : TALER_EXCHANGEDB_get_reserve (pg,
778 : &reserve));
779 1 : FAILIF (0 != TALER_amount_cmp (&reserve.balance,
780 : &expect_reserve));
781 1 : return 0;
782 : }
783 :
784 :
785 : /**
786 : * The checks to run, in order.
787 : */
788 : static const struct TDB_Test tests[] = {
789 : { "recoup-unknown-coin",
790 : &check_unknown_coin },
791 : { "recoup-empty-coin",
792 : &check_empty_coin },
793 : { "recoup-recoup",
794 : &check_recoup },
795 : { "recoup-iterate",
796 : &check_iterate },
797 : { "recoup-replication",
798 : &check_replication },
799 : { "recoup-replay-latest",
800 : &check_replay_latest },
801 : { NULL, NULL }
802 : };
803 :
804 :
805 : int
806 1 : main (int argc,
807 : char *const *argv)
808 : {
809 : int ret;
810 :
811 1 : ret = TDB_main (argc,
812 : argv,
813 : "test-recoup",
814 : "Tests for the exchangedb `recoup' table",
815 : tests);
816 1 : TDB_account_free (&account);
817 1 : TDB_denom_free (&denom);
818 1 : return ret;
819 : }
820 :
821 :
822 : /* end of test_recoup.c */
|