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