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_recoup_refresh.h"
37 : #include "exchange-database/do_refresh.h"
38 : #include "exchange-database/do_refund.h"
39 : #include "exchange-database/get_coin_transactions.h"
40 : #include "exchange-database/insert_reserve_open_deposit.h"
41 : #include "exchange-database/update_to_refresh_revealed.h"
42 :
43 :
44 : /**
45 : * Account the checks fund their reserves from.
46 : */
47 : static struct TDB_Account account;
48 :
49 :
50 : /**
51 : * Denomination the checks use.
52 : */
53 : static struct TDB_Denom denom;
54 :
55 :
56 : /**
57 : * A different denomination used among the refresh outputs.
58 : */
59 : static struct TDB_Denom fresh_denom;
60 :
61 :
62 : /**
63 : * The coin whose history we build up.
64 : */
65 : static struct TALER_CoinPublicInfo coin;
66 :
67 :
68 : /**
69 : * Row of @e coin in `known_coins`.
70 : */
71 : static uint64_t known_coin_id;
72 :
73 :
74 : /**
75 : * Reserve @e coin was withdrawn from.
76 : */
77 : static struct TALER_ReservePublicKeyP reserve_pub;
78 :
79 :
80 : /**
81 : * Row of the withdraw that created @e coin.
82 : */
83 : static uint64_t withdraw_id;
84 :
85 :
86 : /**
87 : * The deposit we later refund.
88 : */
89 : static struct TDB_Deposit deposit;
90 :
91 :
92 : /**
93 : * ETag of the history right after the deposit was refunded.
94 : */
95 : static uint64_t etag_after_refund;
96 :
97 :
98 : /**
99 : * Count the entries of @a tl and remember which types occurred.
100 : *
101 : * @param tl transaction list to walk
102 : * @param[out] types set to the bitmask of the types seen
103 : * @return number of entries in @a tl
104 : */
105 : static unsigned int
106 7 : summarize (const struct TALER_EXCHANGEDB_TransactionList *tl,
107 : unsigned int *types)
108 : {
109 7 : unsigned int cnt = 0;
110 :
111 7 : *types = 0;
112 7 : for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
113 35 : NULL != pos;
114 28 : pos = pos->next)
115 : {
116 28 : cnt++;
117 28 : *types |= 1U << pos->type;
118 : }
119 7 : return cnt;
120 : }
121 :
122 :
123 : /**
124 : * Ask for the full history of @e coin.
125 : *
126 : * @param pg the database context
127 : * @param start_off offset to start from
128 : * @param etag_in ETag the caller already has
129 : * @param[out] etag_out set to the current ETag
130 : * @param[out] tlp set to the history
131 : * @return transaction status
132 : */
133 : static enum GNUNET_DB_QueryStatus
134 8 : history (struct TALER_EXCHANGEDB_PostgresContext *pg,
135 : uint64_t start_off,
136 : uint64_t etag_in,
137 : uint64_t *etag_out,
138 : struct TALER_EXCHANGEDB_TransactionList **tlp)
139 : {
140 : struct TALER_Amount balance;
141 : struct TALER_DenominationHashP h_denom_pub;
142 :
143 8 : return TALER_EXCHANGEDB_get_coin_transactions (pg,
144 : true,
145 : &coin.coin_pub,
146 : start_off,
147 : etag_in,
148 : etag_out,
149 : &balance,
150 : &h_denom_pub,
151 : tlp);
152 : }
153 :
154 :
155 : /**
156 : * A coin the exchange never saw has no history.
157 : *
158 : * @param pg the database context
159 : * @return 0 on success
160 : */
161 : static int
162 1 : check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
163 : {
164 1 : struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
165 : struct TALER_CoinSpendPublicKeyP unknown;
166 : struct TALER_Amount balance;
167 : struct TALER_DenominationHashP h_denom_pub;
168 1 : uint64_t etag = 42;
169 :
170 1 : TDB_account (pg,
171 : 10,
172 : &account);
173 1 : TDB_denom (pg,
174 : 10,
175 : "5",
176 : "0.1",
177 : &denom);
178 1 : TDB_FILL (unknown,
179 : 99);
180 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
181 : TALER_EXCHANGEDB_get_coin_transactions (pg,
182 : true,
183 : &unknown,
184 : 0,
185 : 0,
186 : &etag,
187 : &balance,
188 : &h_denom_pub,
189 : &tl));
190 1 : FAILIF (NULL != tl);
191 1 : return 0;
192 : }
193 :
194 :
195 : /**
196 : * A coin that was withdrawn but never spent has no history either: the
197 : * `withdraw` table has no coin history trigger.
198 : *
199 : * @param pg the database context
200 : * @return 0 on success
201 : */
202 : static int
203 1 : check_fresh_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
204 : {
205 1 : struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
206 1 : uint64_t etag = 42;
207 :
208 1 : TDB_reserve_in (pg,
209 : &account,
210 : 11,
211 : "10",
212 : &reserve_pub);
213 1 : withdraw_id = TDB_withdraw (pg,
214 : &denom,
215 : &reserve_pub,
216 : 11,
217 : "5");
218 1 : TDB_coin (pg,
219 : &denom,
220 : 20,
221 : &coin,
222 : &known_coin_id);
223 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
224 : history (pg,
225 : 0,
226 : 0,
227 : &etag,
228 : &tl));
229 1 : FAILIF (NULL != tl);
230 1 : FAILIF (0 != TDB_count (pg,
231 : "FROM coin_history"));
232 1 : return 0;
233 : }
234 :
235 :
236 : /**
237 : * A deposit and its refund show up as two entries.
238 : *
239 : * @param pg the database context
240 : * @return 0 on success
241 : */
242 : static int
243 1 : check_deposit_and_refund (struct TALER_EXCHANGEDB_PostgresContext *pg)
244 : {
245 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
246 : struct TALER_EXCHANGEDB_Refund refund;
247 1 : struct TALER_Amount deposit_fee = TDB_amount ("0.1");
248 : struct TALER_Amount balance;
249 1 : struct TALER_Amount expect = TDB_amount ("4.5");
250 : struct TALER_DenominationHashP h_denom_pub;
251 : unsigned int types;
252 1 : uint64_t etag = 0;
253 : bool not_found;
254 : bool refund_ok;
255 : bool gone;
256 : bool conflict;
257 :
258 1 : TDB_deposit (pg,
259 : &account,
260 : &coin,
261 : 30,
262 : "1",
263 : "0.1",
264 : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
265 : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
266 : &deposit);
267 1 : FAILIF (1 != TDB_count (pg,
268 : "FROM coin_history"));
269 :
270 1 : memset (&refund,
271 : 0,
272 : sizeof (refund));
273 1 : refund.coin = coin;
274 1 : refund.details.merchant_pub = deposit.merchant_pub;
275 1 : TDB_FILL (refund.details.merchant_sig,
276 : 31);
277 1 : refund.details.h_contract_terms = deposit.h_contract_terms;
278 1 : refund.details.rtransaction_id = 1;
279 1 : refund.details.refund_amount = TDB_amount ("0.5");
280 1 : refund.details.refund_fee = TDB_amount ("0");
281 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
282 : TALER_EXCHANGEDB_do_refund (pg,
283 : &refund,
284 : &deposit_fee,
285 : 0,
286 : ¬_found,
287 : &refund_ok,
288 : &gone,
289 : &conflict));
290 1 : FAILIF (not_found);
291 1 : FAILIF (! refund_ok);
292 1 : FAILIF (2 != TDB_count (pg,
293 : "FROM coin_history"));
294 :
295 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
296 : TALER_EXCHANGEDB_get_coin_transactions (pg,
297 : true,
298 : &coin.coin_pub,
299 : 0,
300 : 0,
301 : &etag,
302 : &balance,
303 : &h_denom_pub,
304 : &tl));
305 1 : FAILIF_C (2 != summarize (tl,
306 : &types),
307 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
308 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_DEPOSIT)),
309 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
310 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
311 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
312 : /* 5 - 1 + 0.5 */
313 1 : FAILIF_C (0 != TALER_amount_cmp (&balance,
314 : &expect),
315 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
316 1 : FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
317 : &denom.h_denom_pub),
318 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
319 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
320 1 : FAILIF (0 == etag);
321 1 : etag_after_refund = etag;
322 1 : return 0;
323 : }
324 :
325 :
326 : /**
327 : * A caller that is already up to date gets no list back, and the
328 : * transaction the lookup opened is not left dangling.
329 : *
330 : * @param pg the database context
331 : * @return 0 on success
332 : */
333 : static int
334 1 : check_etag (struct TALER_EXCHANGEDB_PostgresContext *pg)
335 : {
336 1 : struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1;
337 1 : uint64_t etag = 0;
338 :
339 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
340 : history (pg,
341 : 0,
342 : etag_after_refund,
343 : &etag,
344 : &tl));
345 1 : FAILIF (NULL != tl);
346 1 : FAILIF (etag != etag_after_refund);
347 : /* the lookup must not leave a transaction open (it starts one of its
348 : own when begin_transaction is true) */
349 1 : FAILIF (NULL != pg->transaction_name);
350 :
351 : /* an ETag that is not the current one still returns the history */
352 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
353 : history (pg,
354 : 0,
355 : etag_after_refund - 1,
356 : &etag,
357 : &tl));
358 1 : FAILIF (NULL == tl);
359 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
360 1 : FAILIF (NULL != pg->transaction_name);
361 1 : return 0;
362 : }
363 :
364 :
365 : /**
366 : * The offset skips everything up to and including it.
367 : *
368 : * @param pg the database context
369 : * @return 0 on success
370 : */
371 : static int
372 1 : check_offset (struct TALER_EXCHANGEDB_PostgresContext *pg)
373 : {
374 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
375 : unsigned int types;
376 1 : uint64_t etag = 0;
377 :
378 : /* everything after the deposit: only the refund is left */
379 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
380 : history (pg,
381 : etag_after_refund - 1,
382 : 0,
383 : &etag,
384 : &tl));
385 1 : FAILIF_C (1 != summarize (tl,
386 : &types),
387 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
388 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)),
389 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
390 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
391 :
392 : /* everything after the last entry: nothing, but the ETag is still
393 : reported */
394 1 : tl = (void *) 1;
395 1 : etag = 0;
396 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
397 : history (pg,
398 : etag_after_refund,
399 : 0,
400 : &etag,
401 : &tl));
402 1 : FAILIF (NULL != tl);
403 1 : FAILIF (etag != etag_after_refund);
404 1 : return 0;
405 : }
406 :
407 :
408 : /**
409 : * Spending the coin into a purse, on a reserve and in a melt adds one
410 : * entry each.
411 : *
412 : * @param pg the database context
413 : * @return 0 on success
414 : */
415 : static int
416 1 : check_other_spends (struct TALER_EXCHANGEDB_PostgresContext *pg)
417 : {
418 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
419 : struct TDB_Purse purse;
420 : struct TALER_CoinSpendSignatureP coin_sig;
421 : struct TALER_ReserveSignatureP reserve_sig;
422 1 : struct TALER_Amount one = TDB_amount ("1");
423 1 : struct TALER_Amount zero = TDB_amount ("0");
424 : unsigned int types;
425 1 : uint64_t etag = 0;
426 : bool balance_ok;
427 : bool too_late;
428 : bool conflict;
429 : bool insufficient_funds;
430 :
431 : /* into a purse */
432 1 : TDB_purse (pg,
433 : 40,
434 : "1",
435 : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
436 : &purse);
437 1 : TDB_FILL (coin_sig,
438 : 41);
439 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
440 : TALER_EXCHANGEDB_do_purse_deposit (pg,
441 : &purse.purse_pub,
442 : &coin.coin_pub,
443 : &one,
444 : &coin_sig,
445 : &one,
446 : &balance_ok,
447 : &too_late,
448 : &conflict));
449 1 : FAILIF (! balance_ok);
450 1 : FAILIF (conflict);
451 :
452 : /* to keep a reserve open */
453 1 : TDB_FILL (coin_sig,
454 : 42);
455 1 : TDB_FILL (reserve_sig,
456 : 42);
457 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
458 : TALER_EXCHANGEDB_insert_reserve_open_deposit (pg,
459 : &coin,
460 : &coin_sig,
461 : known_coin_id,
462 : &one,
463 : &reserve_sig,
464 : &reserve_pub,
465 : &insufficient_funds));
466 1 : FAILIF (insufficient_funds);
467 :
468 : /* and into a melt */
469 : {
470 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
471 1 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
472 : struct TALER_Amount coin_balance;
473 : bool found;
474 1 : bool zombie_required = false;
475 : bool nonce_reuse;
476 : bool melt_balance_ok;
477 : uint32_t noreveal_index;
478 : enum GNUNET_DB_QueryStatus qs;
479 :
480 1 : memset (&rf,
481 : 0,
482 : sizeof (rf));
483 1 : rf.coin.coin_pub = coin.coin_pub;
484 1 : rf.coin.denom_pub_hash = coin.denom_pub_hash;
485 1 : rf.coin.no_age_commitment = coin.no_age_commitment;
486 1 : TDB_FILL (rf.coin_sig,
487 : 43);
488 1 : TDB_FILL (rf.rc,
489 : 43);
490 1 : TDB_FILL (rf.refresh_seed,
491 : 43);
492 1 : TDB_FILL (rf.planchets_h,
493 : 43);
494 1 : TDB_FILL (rf.selected_h,
495 : 44);
496 1 : rf.amount_with_fee = one;
497 1 : TDB_denom (pg,
498 : 11,
499 : "0.1",
500 : "0.01",
501 : &fresh_denom);
502 1 : rf.num_coins = 3;
503 1 : rf.denom_serials = GNUNET_new_array (rf.num_coins,
504 : uint64_t);
505 : /* Deliberately neither sorted nor distinct. */
506 1 : rf.denom_serials[0] = fresh_denom.serial;
507 1 : rf.denom_serials[1] = denom.serial;
508 1 : rf.denom_serials[2] = fresh_denom.serial;
509 1 : rf.denom_sigs = GNUNET_new_array (
510 : rf.num_coins,
511 : struct TALER_BlindedDenominationSignature);
512 4 : for (unsigned int i = 0; i < rf.num_coins; i++)
513 3 : TDB_blinded_denom_sig (43 + i,
514 3 : &rf.denom_sigs[i]);
515 1 : rf.noreveal_index = 1;
516 1 : rf.is_v27_refresh = true;
517 1 : rf.no_blinding_seed = true;
518 1 : qs = TALER_EXCHANGEDB_do_refresh (pg,
519 : &rf,
520 : &now,
521 : &found,
522 : &noreveal_index,
523 : &zombie_required,
524 : &nonce_reuse,
525 : &melt_balance_ok,
526 : &coin_balance);
527 4 : for (unsigned int i = 0; i < rf.num_coins; i++)
528 3 : TALER_blinded_denom_sig_free (&rf.denom_sigs[i]);
529 1 : GNUNET_free (rf.denom_sigs);
530 1 : GNUNET_free (rf.denom_serials);
531 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
532 1 : FAILIF (! melt_balance_ok);
533 :
534 : /* The original output list is available before and after reveal. */
535 3 : for (unsigned int revealed = 0; revealed < 2; revealed++)
536 : {
537 2 : bool found_melt = false;
538 :
539 2 : if (revealed)
540 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
541 : TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
542 : &rf.rc));
543 2 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
544 : history (pg,
545 : 0,
546 : 0,
547 : &etag,
548 : &tl));
549 2 : for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
550 12 : NULL != pos;
551 10 : pos = pos->next)
552 : {
553 : const struct TALER_EXCHANGEDB_MeltListEntry *melt;
554 :
555 10 : if (TALER_EXCHANGEDB_TT_MELT != pos->type)
556 8 : continue;
557 2 : found_melt = true;
558 2 : melt = pos->details.melt;
559 2 : FAILIF_C (3 != melt->num_coins,
560 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
561 2 : FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[0],
562 : &fresh_denom.h_denom_pub),
563 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
564 2 : FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[1],
565 : &denom.h_denom_pub),
566 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
567 2 : FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[2],
568 : &fresh_denom.h_denom_pub),
569 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
570 : }
571 2 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
572 2 : tl = NULL;
573 2 : FAILIF (! found_melt);
574 : }
575 : }
576 :
577 1 : FAILIF (5 != TDB_count (pg,
578 : "FROM coin_history"));
579 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
580 : history (pg,
581 : 0,
582 : 0,
583 : &etag,
584 : &tl));
585 1 : FAILIF_C (5 != summarize (tl,
586 : &types),
587 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
588 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)),
589 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
590 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RESERVE_OPEN)),
591 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
592 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_MELT)),
593 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
594 :
595 : /* the ETag is the highest coin history row of the list */
596 : {
597 1 : uint64_t max = 0;
598 :
599 1 : for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
600 6 : NULL != pos;
601 5 : pos = pos->next)
602 5 : max = GNUNET_MAX (max,
603 : pos->coin_history_id);
604 1 : FAILIF_C (etag != max,
605 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
606 : }
607 :
608 : /* the spends add up to what the coin no longer has */
609 : {
610 : struct TALER_Amount total;
611 1 : struct TALER_Amount expect = TDB_amount ("3.5");
612 :
613 1 : FAILIF_C (GNUNET_OK !=
614 : TALER_EXCHANGEDB_calculate_transaction_list_totals (tl,
615 : &zero,
616 : &total),
617 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
618 1 : FAILIF_C (0 != TALER_amount_cmp (&total,
619 : &expect),
620 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
621 : }
622 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
623 1 : return 0;
624 : }
625 :
626 :
627 : /**
628 : * Recouping the coin adds the last entry and empties it.
629 : *
630 : * @param pg the database context
631 : * @return 0 on success
632 : */
633 : static int
634 1 : check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg)
635 : {
636 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
637 : struct TALER_CoinSpendSignatureP coin_sig;
638 : union GNUNET_CRYPTO_BlindingSecretP coin_bks;
639 : struct GNUNET_TIME_Timestamp recoup_timestamp;
640 : struct TALER_Amount recoup_amount;
641 : struct TALER_Amount balance;
642 1 : struct TALER_Amount zero = TDB_amount ("0");
643 : struct TALER_DenominationHashP h_denom_pub;
644 : unsigned int types;
645 1 : uint64_t etag = 0;
646 : bool recoup_ok;
647 : bool internal_failure;
648 :
649 1 : TDB_FILL (coin_sig,
650 : 50);
651 1 : TDB_FILL (coin_bks,
652 : 50);
653 : /* in/out: the caller picks the time, the callee only overwrites it if
654 : the coin was recouped before */
655 1 : recoup_timestamp = GNUNET_TIME_timestamp_get ();
656 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
657 : TALER_EXCHANGEDB_do_recoup (pg,
658 : &reserve_pub,
659 : withdraw_id,
660 : 3,
661 : &coin_bks,
662 : &coin.coin_pub,
663 : known_coin_id,
664 : &coin_sig,
665 : &recoup_timestamp,
666 : &recoup_amount,
667 : &recoup_ok,
668 : &internal_failure));
669 1 : FAILIF (internal_failure);
670 1 : FAILIF (! recoup_ok);
671 1 : FAILIF (6 != TDB_count (pg,
672 : "FROM coin_history"));
673 :
674 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
675 : TALER_EXCHANGEDB_get_coin_transactions (pg,
676 : true,
677 : &coin.coin_pub,
678 : 0,
679 : 0,
680 : &etag,
681 : &balance,
682 : &h_denom_pub,
683 : &tl));
684 1 : FAILIF_C (6 != summarize (tl,
685 : &types),
686 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
687 1 : FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW)),
688 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
689 : /* the entry names the withdraw operation (by its commitment, seeded
690 : like the withdraw in check_fresh_coin()) and the coin's position */
691 : {
692 1 : const struct TALER_EXCHANGEDB_RecoupListEntry *recoup = NULL;
693 : struct TALER_HashBlindedPlanchetsP planchets_h;
694 :
695 1 : TDB_FILL (planchets_h,
696 : 11);
697 1 : for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
698 7 : NULL != pos;
699 6 : pos = pos->next)
700 6 : if (TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW == pos->type)
701 1 : recoup = pos->details.recoup;
702 1 : FAILIF_C (NULL == recoup,
703 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
704 1 : FAILIF_C (3 != recoup->coin_index,
705 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
706 1 : FAILIF_C (0 != GNUNET_memcmp (&recoup->planchets_h,
707 : &planchets_h),
708 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
709 1 : FAILIF_C (0 != GNUNET_memcmp (&recoup->reserve_pub,
710 : &reserve_pub),
711 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
712 1 : FAILIF_C (0 != GNUNET_memcmp (&recoup->coin_sig,
713 : &coin_sig),
714 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
715 1 : FAILIF_C (0 != GNUNET_memcmp (&recoup->coin_blind,
716 : &coin_bks),
717 : TALER_EXCHANGEDB_free_coin_transaction_list (tl));
718 : }
719 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
720 1 : FAILIF (0 != TALER_amount_cmp (&balance,
721 : &zero));
722 1 : return 0;
723 : }
724 :
725 :
726 : /**
727 : * With begin_transaction false the caller's transaction is used and left
728 : * open.
729 : *
730 : * @param pg the database context
731 : * @return 0 on success
732 : */
733 : static int
734 1 : check_in_transaction (struct TALER_EXCHANGEDB_PostgresContext *pg)
735 : {
736 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
737 : struct TALER_Amount balance;
738 : struct TALER_DenominationHashP h_denom_pub;
739 : unsigned int types;
740 1 : uint64_t etag = 0;
741 :
742 1 : FAILIF (GNUNET_OK !=
743 : TALER_EXCHANGEDB_start (pg,
744 : "coin-history"));
745 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
746 : TALER_EXCHANGEDB_get_coin_transactions (pg,
747 : false,
748 : &coin.coin_pub,
749 : 0,
750 : 0,
751 : &etag,
752 : &balance,
753 : &h_denom_pub,
754 : &tl),
755 : TALER_EXCHANGEDB_rollback (pg));
756 1 : FAILIF_C (6 != summarize (tl,
757 : &types),
758 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
759 : TALER_EXCHANGEDB_rollback (pg));
760 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
761 1 : FAILIF_C (NULL == pg->transaction_name,
762 : TALER_EXCHANGEDB_rollback (pg));
763 1 : TALER_EXCHANGEDB_rollback (pg);
764 1 : return 0;
765 : }
766 :
767 :
768 : /**
769 : * Recouping a coin that was refreshed from @e coin credits @e coin and adds
770 : * a RECOUP-REFRESH-RECEIVER entry to its history that names the refresh
771 : * operation (by its commitment, seeded like the melt in
772 : * check_other_spends()), the recouped coin and its position in the batch.
773 : * The recouped coin's own history gets the matching RECOUP-REFRESH entry.
774 : *
775 : * @param pg the database context
776 : * @return 0 on success
777 : */
778 : static int
779 1 : check_recoup_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg)
780 : {
781 1 : struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
782 1 : const struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr = NULL;
783 : struct TALER_CoinPublicInfo fresh_coin;
784 : struct TALER_RefreshCommitmentP rc;
785 : struct TALER_CoinSpendSignatureP coin_sig;
786 : union GNUNET_CRYPTO_BlindingSecretP coin_bks;
787 : struct GNUNET_TIME_Timestamp recoup_timestamp;
788 : struct TALER_Amount recoup_amount;
789 : struct TALER_Amount balance;
790 1 : struct TALER_Amount zero = TDB_amount ("0");
791 1 : struct TALER_Amount credited = TDB_amount ("0.1");
792 : struct TALER_DenominationHashP h_denom_pub;
793 : uint64_t fresh_known_coin_id;
794 : uint64_t refresh_id;
795 1 : uint64_t etag = 0;
796 : unsigned int types;
797 : bool recoup_ok;
798 : bool internal_failure;
799 :
800 1 : TDB_FILL (rc,
801 : 43);
802 : /* row of the melt made in check_other_spends() */
803 : {
804 1 : struct GNUNET_PQ_QueryParam params[] = {
805 1 : GNUNET_PQ_query_param_auto_from_type (&rc),
806 : GNUNET_PQ_query_param_end
807 : };
808 1 : struct GNUNET_PQ_ResultSpec rs[] = {
809 1 : GNUNET_PQ_result_spec_uint64 ("refresh_id",
810 : &refresh_id),
811 : GNUNET_PQ_result_spec_end
812 : };
813 :
814 1 : FAILIF (GNUNET_OK !=
815 : GNUNET_PQ_prepare_anon (pg->conn,
816 : "SELECT refresh_id"
817 : " FROM refresh"
818 : " WHERE rc=$1;"));
819 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
820 : GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
821 : "",
822 : params,
823 : rs));
824 : }
825 1 : TDB_coin (pg,
826 : &fresh_denom,
827 : 60,
828 : &fresh_coin,
829 : &fresh_known_coin_id);
830 1 : TDB_FILL (coin_sig,
831 : 60);
832 1 : TDB_FILL (coin_bks,
833 : 60);
834 1 : recoup_timestamp = GNUNET_TIME_timestamp_get ();
835 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
836 : TALER_EXCHANGEDB_do_recoup_refresh (pg,
837 : &coin.coin_pub,
838 : refresh_id,
839 : 2,
840 : &coin_bks,
841 : &fresh_coin.coin_pub,
842 : fresh_known_coin_id,
843 : &coin_sig,
844 : &recoup_timestamp,
845 : &recoup_amount,
846 : &recoup_ok,
847 : &internal_failure),
848 : TDB_coin_free (&fresh_coin));
849 1 : FAILIF_C (internal_failure || ! recoup_ok,
850 : TDB_coin_free (&fresh_coin));
851 1 : FAILIF_C (0 != TALER_amount_cmp (&recoup_amount,
852 : &credited),
853 : TDB_coin_free (&fresh_coin));
854 :
855 : /* the old coin is credited and sees the receiver entry */
856 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
857 : TALER_EXCHANGEDB_get_coin_transactions (pg,
858 : true,
859 : &coin.coin_pub,
860 : 0,
861 : 0,
862 : &etag,
863 : &balance,
864 : &h_denom_pub,
865 : &tl),
866 : TDB_coin_free (&fresh_coin));
867 1 : FAILIF_C (7 != summarize (tl,
868 : &types),
869 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
870 : TDB_coin_free (&fresh_coin));
871 1 : for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
872 8 : NULL != pos;
873 7 : pos = pos->next)
874 7 : if (TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER == pos->type)
875 1 : rr = pos->details.old_coin_recoup;
876 1 : FAILIF_C (NULL == rr,
877 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
878 : TDB_coin_free (&fresh_coin));
879 1 : FAILIF_C (2 != rr->coin_index,
880 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
881 : TDB_coin_free (&fresh_coin));
882 1 : FAILIF_C (0 != GNUNET_memcmp (&rr->rc,
883 : &rc),
884 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
885 : TDB_coin_free (&fresh_coin));
886 1 : FAILIF_C (0 != GNUNET_memcmp (&rr->coin.coin_pub,
887 : &fresh_coin.coin_pub),
888 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
889 : TDB_coin_free (&fresh_coin));
890 1 : FAILIF_C (0 != GNUNET_memcmp (&rr->old_coin_pub,
891 : &coin.coin_pub),
892 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
893 : TDB_coin_free (&fresh_coin));
894 1 : FAILIF_C (0 != TALER_amount_cmp (&rr->value,
895 : &credited),
896 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
897 : TDB_coin_free (&fresh_coin));
898 1 : FAILIF_C (0 != TALER_amount_cmp (&balance,
899 : &credited),
900 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
901 : TDB_coin_free (&fresh_coin));
902 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
903 1 : tl = NULL;
904 :
905 : /* the recouped coin is emptied and sees the debit entry */
906 1 : etag = 0;
907 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
908 : TALER_EXCHANGEDB_get_coin_transactions (pg,
909 : true,
910 : &fresh_coin.coin_pub,
911 : 0,
912 : 0,
913 : &etag,
914 : &balance,
915 : &h_denom_pub,
916 : &tl),
917 : TDB_coin_free (&fresh_coin));
918 1 : FAILIF_C (1 != summarize (tl,
919 : &types),
920 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
921 : TDB_coin_free (&fresh_coin));
922 1 : FAILIF_C (TALER_EXCHANGEDB_TT_RECOUP_REFRESH != tl->type,
923 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
924 : TDB_coin_free (&fresh_coin));
925 1 : rr = tl->details.recoup_refresh;
926 1 : FAILIF_C (2 != rr->coin_index,
927 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
928 : TDB_coin_free (&fresh_coin));
929 1 : FAILIF_C (0 != GNUNET_memcmp (&rr->rc,
930 : &rc),
931 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
932 : TDB_coin_free (&fresh_coin));
933 1 : FAILIF_C (0 != GNUNET_memcmp (&rr->old_coin_pub,
934 : &coin.coin_pub),
935 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
936 : TDB_coin_free (&fresh_coin));
937 1 : FAILIF_C (0 != GNUNET_memcmp (&rr->coin_sig,
938 : &coin_sig),
939 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
940 : TDB_coin_free (&fresh_coin));
941 1 : FAILIF_C (0 != TALER_amount_cmp (&balance,
942 : &zero),
943 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
944 : TDB_coin_free (&fresh_coin));
945 1 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
946 1 : TDB_coin_free (&fresh_coin);
947 1 : return 0;
948 : }
949 :
950 :
951 : /**
952 : * The checks to run, in order.
953 : */
954 : static const struct TDB_Test tests[] = {
955 : { "coin-history-unknown-coin",
956 : &check_unknown_coin },
957 : { "coin-history-fresh-coin",
958 : &check_fresh_coin },
959 : { "coin-history-deposit-and-refund",
960 : &check_deposit_and_refund },
961 : { "coin-history-etag",
962 : &check_etag },
963 : { "coin-history-offset",
964 : &check_offset },
965 : { "coin-history-other-spends",
966 : &check_other_spends },
967 : { "coin-history-recoup",
968 : &check_recoup },
969 : { "coin-history-in-transaction",
970 : &check_in_transaction },
971 : { "coin-history-recoup-refresh",
972 : &check_recoup_refresh },
973 : { NULL, NULL }
974 : };
975 :
976 :
977 : int
978 1 : main (int argc,
979 : char *const *argv)
980 : {
981 : int ret;
982 :
983 1 : ret = TDB_main (argc,
984 : argv,
985 : "test-coin-history",
986 : "Tests for the exchangedb `coin_history' table",
987 : tests);
988 1 : TDB_coin_free (&coin);
989 1 : TDB_denom_free (&denom);
990 1 : TDB_denom_free (&fresh_denom);
991 1 : TDB_account_free (&account);
992 1 : return ret;
993 : }
994 :
995 :
996 : /* end of test_coin_history.c */
|