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_coin_history.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `coin_history`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_get_coin_transactions().
23 : *
24 : * Nothing writes to `coin_history` directly: every table that can spend or
25 : * credit a coin has an INSERT trigger that appends a row naming itself and
26 : * its own serial. get_coin_transactions() walks those rows and looks each
27 : * one up in the table it names. So the interesting part of this test is
28 : * spending one coin in as many different ways as possible and then checking
29 : * that every one of them comes back with the right type and amount.
30 : */
31 : #include "test_common.h"
32 : #include "exchange-database/do_purse_deposit.h"
33 : #include "exchange-database/rollback.h"
34 : #include "exchange-database/start.h"
35 : #include "exchange-database/do_recoup.h"
36 : #include "exchange-database/do_refresh.h"
37 : #include "exchange-database/do_refund.h"
38 : #include "exchange-database/get_coin_transactions.h"
39 : #include "exchange-database/insert_reserve_open_deposit.h"
40 : #include "exchange-database/update_to_refresh_revealed.h"
41 :
42 :
43 : /**
44 : * Account the checks fund their reserves from.
45 : */
46 : static struct TDB_Account account;
47 :
48 :
49 : /**
50 : * Denomination the checks use.
51 : */
52 : static struct TDB_Denom denom;
53 :
54 :
55 : /**
56 : * A different denomination used among the refresh outputs.
57 : */
58 : static struct TDB_Denom fresh_denom;
59 :
60 :
61 : /**
62 : * The coin whose history we build up.
63 : */
64 : static struct TALER_CoinPublicInfo coin;
65 :
66 :
67 : /**
68 : * Row of @e coin in `known_coins`.
69 : */
70 : static uint64_t known_coin_id;
71 :
72 :
73 : /**
74 : * Reserve @e coin was withdrawn from.
75 : */
76 : static struct TALER_ReservePublicKeyP reserve_pub;
77 :
78 :
79 : /**
80 : * Row of the withdraw that created @e coin.
81 : */
82 : static uint64_t withdraw_id;
83 :
84 :
85 : /**
86 : * The deposit we later refund.
87 : */
88 : static struct TDB_Deposit deposit;
89 :
90 :
91 : /**
92 : * ETag of the history right after the deposit was refunded.
93 : */
94 : static uint64_t etag_after_refund;
95 :
96 :
97 : /**
98 : * Count the entries of @a tl and remember which types occurred.
99 : *
100 : * @param tl transaction list to walk
101 : * @param[out] types set to the bitmask of the types seen
102 : * @return number of entries in @a tl
103 : */
104 : static unsigned int
105 5 : summarize (const struct TALER_EXCHANGEDB_TransactionList *tl,
106 : unsigned int *types)
107 : {
108 5 : unsigned int cnt = 0;
109 :
110 5 : *types = 0;
111 5 : for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
112 25 : NULL != pos;
113 20 : pos = pos->next)
114 : {
115 20 : cnt++;
116 20 : *types |= 1U << pos->type;
117 : }
118 5 : return cnt;
119 : }
120 :
121 :
122 : /**
123 : * Ask for the full history of @e coin.
124 : *
125 : * @param pg the database context
126 : * @param start_off offset to start from
127 : * @param etag_in ETag the caller already has
128 : * @param[out] etag_out set to the current ETag
129 : * @param[out] tlp set to the history
130 : * @return transaction status
131 : */
132 : static enum GNUNET_DB_QueryStatus
133 8 : history (struct TALER_EXCHANGEDB_PostgresContext *pg,
134 : uint64_t start_off,
135 : uint64_t etag_in,
136 : uint64_t *etag_out,
137 : struct TALER_EXCHANGEDB_TransactionList **tlp)
138 : {
139 : struct TALER_Amount balance;
140 : struct TALER_DenominationHashP h_denom_pub;
141 :
142 8 : return TALER_EXCHANGEDB_get_coin_transactions (pg,
143 : true,
144 : &coin.coin_pub,
145 : start_off,
146 : etag_in,
147 : etag_out,
148 : &balance,
149 : &h_denom_pub,
150 : tlp);
151 : }
152 :
153 :
154 : /**
155 : * A coin the exchange never saw has no history.
156 : *
157 : * @param pg the database context
158 : * @return 0 on success
159 : */
160 : static int
161 1 : check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
162 : {
163 1 : struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
164 : struct TALER_CoinSpendPublicKeyP unknown;
165 : struct TALER_Amount balance;
166 : struct TALER_DenominationHashP h_denom_pub;
167 1 : uint64_t etag = 42;
168 :
169 1 : TDB_account (pg,
170 : 10,
171 : &account);
172 1 : TDB_denom (pg,
173 : 10,
174 : "5",
175 : "0.1",
176 : &denom);
177 1 : TDB_FILL (unknown,
178 : 99);
179 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
180 : TALER_EXCHANGEDB_get_coin_transactions (pg,
181 : true,
182 : &unknown,
183 : 0,
184 : 0,
185 : &etag,
186 : &balance,
187 : &h_denom_pub,
188 : &tl));
189 1 : FAILIF (NULL != tl);
190 1 : return 0;
191 : }
192 :
193 :
194 : /**
195 : * A coin that was withdrawn but never spent has no history either: the
196 : * `withdraw` table has no coin history trigger.
197 : *
198 : * @param pg the database context
199 : * @return 0 on success
200 : */
201 : static int
202 1 : check_fresh_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
203 : {
204 1 : struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
205 1 : uint64_t etag = 42;
206 :
207 1 : TDB_reserve_in (pg,
208 : &account,
209 : 11,
210 : "10",
211 : &reserve_pub);
212 1 : withdraw_id = TDB_withdraw (pg,
213 : &denom,
214 : &reserve_pub,
215 : 11,
216 : "5");
217 1 : TDB_coin (pg,
218 : &denom,
219 : 20,
220 : &coin,
221 : &known_coin_id);
222 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
223 : history (pg,
224 : 0,
225 : 0,
226 : &etag,
227 : &tl));
228 1 : FAILIF (NULL != tl);
229 1 : FAILIF (0 != TDB_count (pg,
230 : "FROM coin_history"));
231 1 : return 0;
232 : }
233 :
234 :
235 : /**
236 : * A deposit and its refund show up as two entries.
237 : *
238 : * @param pg the database context
239 : * @return 0 on success
240 : */
241 : static int
242 1 : check_deposit_and_refund (struct TALER_EXCHANGEDB_PostgresContext *pg)
243 : {
244 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
245 : struct TALER_EXCHANGEDB_Refund refund;
246 1 : struct TALER_Amount deposit_fee = TDB_amount ("0.1");
247 : struct TALER_Amount balance;
248 1 : struct TALER_Amount expect = TDB_amount ("4.5");
249 : struct TALER_DenominationHashP h_denom_pub;
250 : unsigned int types;
251 1 : uint64_t etag = 0;
252 : bool not_found;
253 : bool refund_ok;
254 : bool gone;
255 : bool conflict;
256 :
257 1 : TDB_deposit (pg,
258 : &account,
259 : &coin,
260 : 30,
261 : "1",
262 : "0.1",
263 : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
264 : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
265 : &deposit);
266 1 : FAILIF (1 != TDB_count (pg,
267 : "FROM coin_history"));
268 :
269 1 : memset (&refund,
270 : 0,
271 : sizeof (refund));
272 1 : refund.coin = coin;
273 1 : refund.details.merchant_pub = deposit.merchant_pub;
274 1 : TDB_FILL (refund.details.merchant_sig,
275 : 31);
276 1 : refund.details.h_contract_terms = deposit.h_contract_terms;
277 1 : refund.details.rtransaction_id = 1;
278 1 : refund.details.refund_amount = TDB_amount ("0.5");
279 1 : refund.details.refund_fee = TDB_amount ("0");
280 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
281 : TALER_EXCHANGEDB_do_refund (pg,
282 : &refund,
283 : &deposit_fee,
284 : 0,
285 : ¬_found,
286 : &refund_ok,
287 : &gone,
288 : &conflict));
289 1 : FAILIF (not_found);
290 1 : FAILIF (! refund_ok);
291 1 : FAILIF (2 != TDB_count (pg,
292 : "FROM coin_history"));
293 :
294 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
295 : TALER_EXCHANGEDB_get_coin_transactions (pg,
296 : true,
297 : &coin.coin_pub,
298 : 0,
299 : 0,
300 : &etag,
301 : &balance,
302 : &h_denom_pub,
303 : &tl));
304 1 : FAILIF_C (2 != summarize (tl,
305 : &types),
306 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
307 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_DEPOSIT)),
308 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
309 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
310 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
311 : /* 5 - 1 + 0.5 */
312 1 : FAILIF_C (0 != TALER_amount_cmp (&balance,
313 : &expect),
314 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
315 1 : FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
316 : &denom.h_denom_pub),
317 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
318 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
319 1 : FAILIF (0 == etag);
320 1 : etag_after_refund = etag;
321 1 : return 0;
322 : }
323 :
324 :
325 : /**
326 : * A caller that is already up to date gets no list back, and the
327 : * transaction the lookup opened is not left dangling.
328 : *
329 : * @param pg the database context
330 : * @return 0 on success
331 : */
332 : static int
333 1 : check_etag (struct TALER_EXCHANGEDB_PostgresContext *pg)
334 : {
335 1 : struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
336 1 : uint64_t etag = 0;
337 :
338 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
339 : history (pg,
340 : 0,
341 : etag_after_refund,
342 : &etag,
343 : &tl));
344 1 : FAILIF (NULL != tl);
345 1 : FAILIF (etag != etag_after_refund);
346 : /* the lookup must not leave a transaction open (it starts one of its
347 : own when begin_transaction is true) */
348 1 : FAILIF (NULL != pg->transaction_name);
349 :
350 : /* an ETag that is not the current one still returns the history */
351 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
352 : history (pg,
353 : 0,
354 : etag_after_refund - 1,
355 : &etag,
356 : &tl));
357 1 : FAILIF (NULL == tl);
358 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
359 1 : FAILIF (NULL != pg->transaction_name);
360 1 : return 0;
361 : }
362 :
363 :
364 : /**
365 : * The offset skips everything up to and including it.
366 : *
367 : * @param pg the database context
368 : * @return 0 on success
369 : */
370 : static int
371 1 : check_offset (struct TALER_EXCHANGEDB_PostgresContext *pg)
372 : {
373 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
374 : unsigned int types;
375 1 : uint64_t etag = 0;
376 :
377 : /* everything after the deposit: only the refund is left */
378 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
379 : history (pg,
380 : etag_after_refund - 1,
381 : 0,
382 : &etag,
383 : &tl));
384 1 : FAILIF_C (1 != summarize (tl,
385 : &types),
386 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
387 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
388 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
389 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
390 :
391 : /* everything after the last entry: nothing, but the ETag is still
392 : reported */
393 1 : tl = (void *) 1;
394 1 : etag = 0;
395 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
396 : history (pg,
397 : etag_after_refund,
398 : 0,
399 : &etag,
400 : &tl));
401 1 : FAILIF (NULL != tl);
402 1 : FAILIF (etag != etag_after_refund);
403 1 : return 0;
404 : }
405 :
406 :
407 : /**
408 : * Spending the coin into a purse, on a reserve and in a melt adds one
409 : * entry each.
410 : *
411 : * @param pg the database context
412 : * @return 0 on success
413 : */
414 : static int
415 1 : check_other_spends (struct TALER_EXCHANGEDB_PostgresContext *pg)
416 : {
417 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
418 : struct TDB_Purse purse;
419 : struct TALER_CoinSpendSignatureP coin_sig;
420 : struct TALER_ReserveSignatureP reserve_sig;
421 1 : struct TALER_Amount one = TDB_amount ("1");
422 1 : struct TALER_Amount zero = TDB_amount ("0");
423 : unsigned int types;
424 1 : uint64_t etag = 0;
425 : bool balance_ok;
426 : bool too_late;
427 : bool conflict;
428 : bool insufficient_funds;
429 :
430 : /* into a purse */
431 1 : TDB_purse (pg,
432 : 40,
433 : "1",
434 : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
435 : &purse);
436 1 : TDB_FILL (coin_sig,
437 : 41);
438 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
439 : TALER_EXCHANGEDB_do_purse_deposit (pg,
440 : &purse.purse_pub,
441 : &coin.coin_pub,
442 : &one,
443 : &coin_sig,
444 : &one,
445 : &balance_ok,
446 : &too_late,
447 : &conflict));
448 1 : FAILIF (! balance_ok);
449 1 : FAILIF (conflict);
450 :
451 : /* to keep a reserve open */
452 1 : TDB_FILL (coin_sig,
453 : 42);
454 1 : TDB_FILL (reserve_sig,
455 : 42);
456 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
457 : TALER_EXCHANGEDB_insert_reserve_open_deposit (pg,
458 : &coin,
459 : &coin_sig,
460 : known_coin_id,
461 : &one,
462 : &reserve_sig,
463 : &reserve_pub,
464 : &insufficient_funds));
465 1 : FAILIF (insufficient_funds);
466 :
467 : /* and into a melt */
468 : {
469 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
470 1 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
471 : struct TALER_Amount coin_balance;
472 : bool found;
473 1 : bool zombie_required = false;
474 : bool nonce_reuse;
475 : bool melt_balance_ok;
476 : uint32_t noreveal_index;
477 : enum GNUNET_DB_QueryStatus qs;
478 :
479 1 : memset (&rf,
480 : 0,
481 : sizeof (rf));
482 1 : rf.coin.coin_pub = coin.coin_pub;
483 1 : rf.coin.denom_pub_hash = coin.denom_pub_hash;
484 1 : rf.coin.no_age_commitment = coin.no_age_commitment;
485 1 : TDB_FILL (rf.coin_sig,
486 : 43);
487 1 : TDB_FILL (rf.rc,
488 : 43);
489 1 : TDB_FILL (rf.refresh_seed,
490 : 43);
491 1 : TDB_FILL (rf.planchets_h,
492 : 43);
493 1 : TDB_FILL (rf.selected_h,
494 : 44);
495 1 : rf.amount_with_fee = one;
496 1 : TDB_denom (pg,
497 : 11,
498 : "0.1",
499 : "0.01",
500 : &fresh_denom);
501 1 : rf.num_coins = 3;
502 1 : rf.denom_serials = GNUNET_new_array (rf.num_coins,
503 : uint64_t);
504 : /* Deliberately neither sorted nor distinct. */
505 1 : rf.denom_serials[0] = fresh_denom.serial;
506 1 : rf.denom_serials[1] = denom.serial;
507 1 : rf.denom_serials[2] = fresh_denom.serial;
508 1 : rf.denom_sigs = GNUNET_new_array (
509 : rf.num_coins,
510 : struct TALER_BlindedDenominationSignature);
511 4 : for (unsigned int i = 0; i < rf.num_coins; i++)
512 3 : TDB_blinded_denom_sig (43 + i,
513 3 : &rf.denom_sigs[i]);
514 1 : rf.noreveal_index = 1;
515 1 : rf.is_v27_refresh = true;
516 1 : rf.no_blinding_seed = true;
517 1 : qs = TALER_EXCHANGEDB_do_refresh (pg,
518 : &rf,
519 : &now,
520 : &found,
521 : &noreveal_index,
522 : &zombie_required,
523 : &nonce_reuse,
524 : &melt_balance_ok,
525 : &coin_balance);
526 4 : for (unsigned int i = 0; i < rf.num_coins; i++)
527 3 : TALER_blinded_denom_sig_free (&rf.denom_sigs[i]);
528 1 : GNUNET_free (rf.denom_sigs);
529 1 : GNUNET_free (rf.denom_serials);
530 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
531 1 : FAILIF (! melt_balance_ok);
532 :
533 : /* The original output list is available before and after reveal. */
534 3 : for (unsigned int revealed = 0; revealed < 2; revealed++)
535 : {
536 2 : bool found_melt = false;
537 :
538 2 : if (revealed)
539 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
540 : TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
541 : &rf.rc));
542 2 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
543 : history (pg,
544 : 0,
545 : 0,
546 : &etag,
547 : &tl));
548 2 : for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
549 12 : NULL != pos;
550 10 : pos = pos->next)
551 : {
552 : const struct TALER_EXCHANGEDB_MeltListEntry *melt;
553 :
554 10 : if (TALER_EXCHANGEDB_TT_MELT != pos->type)
555 8 : continue;
556 2 : found_melt = true;
557 2 : melt = pos->details.melt;
558 2 : FAILIF_C (3 != melt->num_coins,
559 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
560 2 : FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[0],
561 : &fresh_denom.h_denom_pub),
562 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
563 2 : FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[1],
564 : &denom.h_denom_pub),
565 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
566 2 : FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[2],
567 : &fresh_denom.h_denom_pub),
568 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
569 : }
570 2 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
571 2 : tl = NULL;
572 2 : FAILIF (! found_melt);
573 : }
574 : }
575 :
576 1 : FAILIF (5 != TDB_count (pg,
577 : "FROM coin_history"));
578 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
579 : history (pg,
580 : 0,
581 : 0,
582 : &etag,
583 : &tl));
584 1 : FAILIF_C (5 != summarize (tl,
585 : &types),
586 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
587 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)),
588 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
589 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RESERVE_OPEN)),
590 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
591 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_MELT)),
592 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
593 :
594 : /* the ETag is the highest coin history row of the list */
595 : {
596 1 : uint64_t max = 0;
597 :
598 1 : for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
599 6 : NULL != pos;
600 5 : pos = pos->next)
601 5 : max = GNUNET_MAX (max,
602 : pos->coin_history_id);
603 1 : FAILIF_C (etag != max,
604 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
605 : }
606 :
607 : /* the spends add up to what the coin no longer has */
608 : {
609 : struct TALER_Amount total;
610 1 : struct TALER_Amount expect = TDB_amount ("3.5");
611 :
612 1 : FAILIF_C (GNUNET_OK !=
613 : TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
614 : &zero,
615 : &total),
616 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
617 1 : FAILIF_C (0 != TALER_amount_cmp (&total,
618 : &expect),
619 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
620 : }
621 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
622 1 : return 0;
623 : }
624 :
625 :
626 : /**
627 : * Recouping the coin adds the last entry and empties it.
628 : *
629 : * @param pg the database context
630 : * @return 0 on success
631 : */
632 : static int
633 1 : check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg)
634 : {
635 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
636 : struct TALER_CoinSpendSignatureP coin_sig;
637 : union GNUNET_CRYPTO_BlindingSecretP coin_bks;
638 : struct GNUNET_TIME_Timestamp recoup_timestamp;
639 : struct TALER_Amount balance;
640 1 : struct TALER_Amount zero = TDB_amount ("0");
641 : struct TALER_DenominationHashP h_denom_pub;
642 : unsigned int types;
643 1 : uint64_t etag = 0;
644 : bool recoup_ok;
645 : bool internal_failure;
646 :
647 1 : TDB_FILL (coin_sig,
648 : 50);
649 1 : TDB_FILL (coin_bks,
650 : 50);
651 : /* in/out: the caller picks the time, the callee only overwrites it if
652 : the coin was recouped before */
653 1 : recoup_timestamp = GNUNET_TIME_timestamp_get ();
654 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
655 : TALER_EXCHANGEDB_do_recoup (pg,
656 : &reserve_pub,
657 : withdraw_id,
658 : &coin_bks,
659 : &coin.coin_pub,
660 : known_coin_id,
661 : &coin_sig,
662 : &recoup_timestamp,
663 : &recoup_ok,
664 : &internal_failure));
665 1 : FAILIF (internal_failure);
666 1 : FAILIF (! recoup_ok);
667 1 : FAILIF (6 != TDB_count (pg,
668 : "FROM coin_history"));
669 :
670 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
671 : TALER_EXCHANGEDB_get_coin_transactions (pg,
672 : true,
673 : &coin.coin_pub,
674 : 0,
675 : 0,
676 : &etag,
677 : &balance,
678 : &h_denom_pub,
679 : &tl));
680 1 : FAILIF_C (6 != summarize (tl,
681 : &types),
682 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
683 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW)),
684 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
685 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
686 1 : FAILIF (0 != TALER_amount_cmp (&balance,
687 : &zero));
688 1 : return 0;
689 : }
690 :
691 :
692 : /**
693 : * With begin_transaction false the caller's transaction is used and left
694 : * open.
695 : *
696 : * @param pg the database context
697 : * @return 0 on success
698 : */
699 : static int
700 1 : check_in_transaction (struct TALER_EXCHANGEDB_PostgresContext *pg)
701 : {
702 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
703 : struct TALER_Amount balance;
704 : struct TALER_DenominationHashP h_denom_pub;
705 : unsigned int types;
706 1 : uint64_t etag = 0;
707 :
708 1 : FAILIF (GNUNET_OK !=
709 : TALER_EXCHANGEDB_start (pg,
710 : "coin-history"));
711 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
712 : TALER_EXCHANGEDB_get_coin_transactions (pg,
713 : false,
714 : &coin.coin_pub,
715 : 0,
716 : 0,
717 : &etag,
718 : &balance,
719 : &h_denom_pub,
720 : &tl),
721 : TALER_EXCHANGEDB_rollback (pg));
722 1 : FAILIF_C (6 != summarize (tl,
723 : &types),
724 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
725 : TALER_EXCHANGEDB_rollback (pg));
726 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
727 1 : FAILIF_C (NULL == pg->transaction_name,
728 : TALER_EXCHANGEDB_rollback (pg));
729 1 : TALER_EXCHANGEDB_rollback (pg);
730 1 : return 0;
731 : }
732 :
733 :
734 : /**
735 : * The checks to run, in order.
736 : */
737 : static const struct TDB_Test tests[] = {
738 : { "coin-history-unknown-coin",
739 : &check_unknown_coin },
740 : { "coin-history-fresh-coin",
741 : &check_fresh_coin },
742 : { "coin-history-deposit-and-refund",
743 : &check_deposit_and_refund },
744 : { "coin-history-etag",
745 : &check_etag },
746 : { "coin-history-offset",
747 : &check_offset },
748 : { "coin-history-other-spends",
749 : &check_other_spends },
750 : { "coin-history-recoup",
751 : &check_recoup },
752 : { "coin-history-in-transaction",
753 : &check_in_transaction },
754 : { NULL, NULL }
755 : };
756 :
757 :
758 : int
759 1 : main (int argc,
760 : char *const *argv)
761 : {
762 : int ret;
763 :
764 1 : ret = TDB_main (argc,
765 : argv,
766 : "test-coin-history",
767 : "Tests for the exchangedb `coin_history' table",
768 : tests);
769 1 : TDB_coin_free (&coin);
770 1 : TDB_denom_free (&denom);
771 1 : TDB_denom_free (&fresh_denom);
772 1 : TDB_account_free (&account);
773 1 : return ret;
774 : }
775 :
776 :
777 : /* end of test_coin_history.c */
|