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_purse_deposits.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `purse_deposits`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_do_purse_deposit(),
23 : * #TALER_EXCHANGEDB_get_purse_deposit(),
24 : * #TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id() and
25 : * #TALER_EXCHANGEDB_iterate_purse_deposits_by_purse().
26 : *
27 : * `purse_deposits` references `purse_requests` and `known_coins`, which
28 : * TDB_purse() and TDB_coin() create. do_purse_deposit() declines in three
29 : * ways: the coin has too little left, the purse has already been decided
30 : * (too late), and the same coin was already paid into the purse with a
31 : * different amount (conflict).
32 : */
33 : #include "test_common.h"
34 : #include "exchange-database/do_expire_purse.h"
35 : #include "exchange-database/do_purse_deposit.h"
36 : #include "exchange-database/get_purse_deposit.h"
37 : #include "exchange-database/iterate_purse_deposits_above_serial_id.h"
38 : #include "exchange-database/iterate_purse_deposits_by_purse.h"
39 : #include "exchange-database/rollback.h"
40 : #include "exchange-database/start.h"
41 :
42 :
43 : /**
44 : * Denomination the checks deposit.
45 : */
46 : static struct TDB_Denom denom;
47 :
48 :
49 : /**
50 : * Build a timestamp from a number of seconds since the epoch.
51 : *
52 : * @param secs seconds since the epoch
53 : * @return the timestamp
54 : */
55 : static struct GNUNET_TIME_Timestamp
56 4 : ts (uint64_t secs)
57 : {
58 4 : struct GNUNET_TIME_Absolute abs = {
59 4 : .abs_value_us = secs * 1000LLU * 1000LLU
60 : };
61 :
62 4 : return GNUNET_TIME_absolute_to_timestamp (abs);
63 : }
64 :
65 :
66 : /**
67 : * Closure for #deposit_cb() and #refund_cb().
68 : */
69 : struct DepositContext
70 : {
71 : /**
72 : * How many rows did the callback see?
73 : */
74 : unsigned int total;
75 :
76 : /**
77 : * Stop after this many rows; 0 for no limit.
78 : */
79 : unsigned int stop_after;
80 :
81 : /**
82 : * Coin we are looking for, NULL to match nothing.
83 : */
84 : const struct TALER_CoinSpendPublicKeyP *coin_pub;
85 :
86 : /**
87 : * How many times did we see it?
88 : */
89 : unsigned int matched;
90 :
91 : /**
92 : * Amount reported for it.
93 : */
94 : struct TALER_Amount amount;
95 :
96 : /**
97 : * Was a reserve reported for its purse?
98 : */
99 : bool have_reserve;
100 : };
101 :
102 :
103 : /**
104 : * Callback for
105 : * #TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id().
106 : *
107 : * @param cls a `struct DepositContext *`
108 : * @param rowid row of the deposit
109 : * @param deposit details of the deposit
110 : * @param reserve_pub reserve the purse was merged into, NULL if none
111 : * @param flags flags of the purse
112 : * @param auditor_balance balance of the purse
113 : * @param purse_total target amount of the purse
114 : * @param denom_pub denomination of the coin
115 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
116 : */
117 : static enum GNUNET_GenericReturnValue
118 1 : deposit_cb (void *cls,
119 : uint64_t rowid,
120 : const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
121 : const struct TALER_ReservePublicKeyP *reserve_pub,
122 : enum TALER_WalletAccountMergeFlags flags,
123 : const struct TALER_Amount *auditor_balance,
124 : const struct TALER_Amount *purse_total,
125 : const struct TALER_DenominationPublicKey *denom_pub)
126 : {
127 1 : struct DepositContext *ctx = cls;
128 :
129 : (void) rowid;
130 : (void) flags;
131 : (void) auditor_balance;
132 : (void) purse_total;
133 : (void) denom_pub;
134 1 : ctx->total++;
135 1 : if ( (NULL != ctx->coin_pub) &&
136 1 : (0 == GNUNET_memcmp (&deposit->coin_pub,
137 : ctx->coin_pub)) )
138 : {
139 1 : ctx->matched++;
140 1 : ctx->amount = deposit->amount;
141 1 : ctx->have_reserve = (NULL != reserve_pub);
142 : }
143 1 : if ( (0 != ctx->stop_after) &&
144 0 : (ctx->total >= ctx->stop_after) )
145 0 : return GNUNET_SYSERR;
146 1 : return GNUNET_OK;
147 : }
148 :
149 :
150 : /**
151 : * Callback for #TALER_EXCHANGEDB_iterate_purse_deposits_by_purse().
152 : *
153 : * @param cls a `struct DepositContext *`
154 : * @param rowid row of the deposit
155 : * @param amount_with_fee how much was paid in
156 : * @param coin_pub which coin paid
157 : * @param denom_pub denomination of that coin
158 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
159 : */
160 : static enum GNUNET_GenericReturnValue
161 1 : refund_cb (void *cls,
162 : uint64_t rowid,
163 : const struct TALER_Amount *amount_with_fee,
164 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
165 : const struct TALER_DenominationPublicKey *denom_pub)
166 : {
167 1 : struct DepositContext *ctx = cls;
168 :
169 : (void) rowid;
170 : (void) denom_pub;
171 1 : ctx->total++;
172 1 : if ( (NULL != ctx->coin_pub) &&
173 1 : (0 == GNUNET_memcmp (coin_pub,
174 : ctx->coin_pub)) )
175 : {
176 1 : ctx->matched++;
177 1 : ctx->amount = *amount_with_fee;
178 : }
179 1 : if ( (0 != ctx->stop_after) &&
180 0 : (ctx->total >= ctx->stop_after) )
181 0 : return GNUNET_SYSERR;
182 1 : return GNUNET_OK;
183 : }
184 :
185 :
186 : /**
187 : * Outcome of a purse deposit.
188 : */
189 : struct DepositStatus
190 : {
191 : /**
192 : * Did the coin have enough left?
193 : */
194 : bool balance_ok;
195 :
196 : /**
197 : * Was the purse already decided?
198 : */
199 : bool too_late;
200 :
201 : /**
202 : * Was the coin already paid into this purse differently?
203 : */
204 : bool conflict;
205 : };
206 :
207 :
208 : /**
209 : * Pay a coin into a purse.
210 : *
211 : * @param pg the database context
212 : * @param purse purse to credit
213 : * @param coin coin to debit
214 : * @param seed seed for the coin signature
215 : * @param amount how much to take from the coin, e.g. "1"
216 : * @param fee purse deposit fee included in @a amount, e.g. "0"
217 : * @param[out] st set to the outcome
218 : * @return transaction status
219 : */
220 : static enum GNUNET_DB_QueryStatus
221 8 : run_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg,
222 : const struct TDB_Purse *purse,
223 : const struct TALER_CoinPublicInfo *coin,
224 : uint32_t seed,
225 : const char *amount,
226 : const char *fee,
227 : struct DepositStatus *st)
228 : {
229 : struct TALER_CoinSpendSignatureP coin_sig;
230 8 : struct TALER_Amount a = TDB_amount (amount);
231 8 : struct TALER_Amount f = TDB_amount (fee);
232 : struct TALER_Amount net;
233 :
234 8 : TDB_fill (&coin_sig,
235 : sizeof (coin_sig),
236 : seed);
237 8 : GNUNET_assert (0 <=
238 : TALER_amount_subtract (&net,
239 : &a,
240 : &f));
241 8 : memset (st,
242 : 0,
243 : sizeof (*st));
244 8 : return TALER_EXCHANGEDB_do_purse_deposit (pg,
245 : &purse->purse_pub,
246 : &coin->coin_pub,
247 : &a,
248 : &coin_sig,
249 : &net,
250 : &st->balance_ok,
251 : &st->too_late,
252 : &st->conflict);
253 : }
254 :
255 :
256 : /**
257 : * Nothing is reported while the table is empty.
258 : *
259 : * @param pg the database context
260 : * @return 0 on success
261 : */
262 : static int
263 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
264 : {
265 : struct TALER_PurseContractPublicKeyP purse_pub;
266 : struct TALER_CoinSpendPublicKeyP coin_pub;
267 : struct TALER_Amount amount;
268 : struct TALER_DenominationHashP h_denom_pub;
269 : struct TALER_AgeCommitmentHashP phac;
270 : struct TALER_CoinSpendSignatureP coin_sig;
271 1 : struct DepositContext ctx = { 0 };
272 1 : char *partner_url = NULL;
273 : bool no_age_commitment;
274 :
275 1 : TDB_denom (pg,
276 : 10,
277 : "5",
278 : "0.1",
279 : &denom);
280 1 : TDB_FILL (purse_pub,
281 : 1);
282 1 : TDB_FILL (coin_pub,
283 : 1);
284 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
285 : TALER_EXCHANGEDB_get_purse_deposit (pg,
286 : &purse_pub,
287 : &coin_pub,
288 : &amount,
289 : &h_denom_pub,
290 : &phac,
291 : &no_age_commitment,
292 : &coin_sig,
293 : &partner_url));
294 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
295 : TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id (
296 : pg,
297 : 0,
298 : &deposit_cb,
299 : &ctx));
300 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
301 : TALER_EXCHANGEDB_iterate_purse_deposits_by_purse (pg,
302 : &purse_pub,
303 : &refund_cb,
304 : &ctx));
305 1 : FAILIF (0 != ctx.total);
306 1 : return 0;
307 : }
308 :
309 :
310 : /**
311 : * Paying a coin into a purse debits the coin and credits the purse.
312 : *
313 : * @param pg the database context
314 : * @return 0 on success
315 : */
316 : static int
317 1 : check_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg)
318 : {
319 : struct TDB_Purse purse;
320 : struct TALER_CoinPublicInfo coin;
321 : struct DepositStatus st;
322 : struct TALER_Amount amount;
323 1 : struct TALER_Amount expect = TDB_amount ("1");
324 : struct TALER_DenominationHashP h_denom_pub;
325 : struct TALER_AgeCommitmentHashP phac;
326 : struct TALER_CoinSpendSignatureP coin_sig;
327 : struct TALER_CoinSpendSignatureP expect_sig;
328 1 : char *partner_url = NULL;
329 1 : bool no_age_commitment = false;
330 :
331 1 : TDB_purse (pg,
332 : 10,
333 : "5",
334 : ts (1700000000),
335 : &purse);
336 1 : TDB_coin (pg,
337 : &denom,
338 : 20,
339 : &coin,
340 : NULL);
341 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
342 : run_deposit (pg,
343 : &purse,
344 : &coin,
345 : 20,
346 : "1",
347 : "0",
348 : &st),
349 : TDB_coin_free (&coin));
350 1 : FAILIF_C (! st.balance_ok,
351 : TDB_coin_free (&coin));
352 1 : FAILIF_C (st.too_late,
353 : TDB_coin_free (&coin));
354 1 : FAILIF_C (st.conflict,
355 : TDB_coin_free (&coin));
356 1 : FAILIF_C (1 != TDB_count (pg,
357 : "FROM purse_deposits"),
358 : TDB_coin_free (&coin));
359 :
360 : /* the coin was debited... */
361 : {
362 1 : char *hex = TDB_hex (&coin.coin_pub,
363 : sizeof (coin.coin_pub));
364 :
365 1 : FAILIF_C (1 != TDB_count (pg,
366 : "FROM known_coins"
367 : " WHERE coin_pub=decode('%s','hex')"
368 : " AND remaining=ROW(4,0)::taler_amount",
369 : hex),
370 : GNUNET_free (hex); TDB_coin_free (&coin));
371 1 : GNUNET_free (hex);
372 : }
373 : /* ...and the purse credited */
374 : {
375 1 : char *hex = TDB_hex (&purse.purse_pub,
376 : sizeof (purse.purse_pub));
377 :
378 1 : FAILIF_C (1 != TDB_count (pg,
379 : "FROM purse_requests"
380 : " WHERE purse_pub=decode('%s','hex')"
381 : " AND balance=ROW(1,0)::taler_amount",
382 : hex),
383 : GNUNET_free (hex); TDB_coin_free (&coin));
384 1 : GNUNET_free (hex);
385 : }
386 :
387 1 : TDB_FILL (expect_sig,
388 : 20);
389 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
390 : TALER_EXCHANGEDB_get_purse_deposit (pg,
391 : &purse.purse_pub,
392 : &coin.coin_pub,
393 : &amount,
394 : &h_denom_pub,
395 : &phac,
396 : &no_age_commitment,
397 : &coin_sig,
398 : &partner_url),
399 : TDB_coin_free (&coin));
400 1 : FAILIF_C (0 != TALER_amount_cmp (&amount,
401 : &expect),
402 : GNUNET_free (partner_url); TDB_coin_free (&coin));
403 1 : FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
404 : &denom.h_denom_pub),
405 : GNUNET_free (partner_url); TDB_coin_free (&coin));
406 1 : FAILIF_C (0 != GNUNET_memcmp (&coin_sig,
407 : &expect_sig),
408 : GNUNET_free (partner_url); TDB_coin_free (&coin));
409 1 : FAILIF_C (! no_age_commitment,
410 : GNUNET_free (partner_url); TDB_coin_free (&coin));
411 : /* the purse is ours, so there is no partner exchange */
412 1 : FAILIF_C (NULL != partner_url,
413 : GNUNET_free (partner_url); TDB_coin_free (&coin));
414 :
415 : /* re-depositing the same coin with the same amount is idempotent */
416 1 : FAILIF_C (0 >
417 : run_deposit (pg,
418 : &purse,
419 : &coin,
420 : 20,
421 : "1",
422 : "0",
423 : &st),
424 : TDB_coin_free (&coin));
425 1 : FAILIF_C (1 != TDB_count (pg,
426 : "FROM purse_deposits"),
427 : TDB_coin_free (&coin));
428 :
429 : /* ...but a second attempt with a different coin signature is a conflict:
430 : the idempotency check compares the signature, not the amount */
431 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
432 : run_deposit (pg,
433 : &purse,
434 : &coin,
435 : 29,
436 : "1",
437 : "0",
438 : &st),
439 : TDB_coin_free (&coin));
440 1 : FAILIF_C (! st.conflict,
441 : TDB_coin_free (&coin));
442 1 : FAILIF_C (1 != TDB_count (pg,
443 : "FROM purse_deposits"),
444 : TDB_coin_free (&coin));
445 1 : TDB_coin_free (&coin);
446 1 : return 0;
447 : }
448 :
449 :
450 : /**
451 : * A coin without enough left cannot be paid into a purse.
452 : *
453 : * @param pg the database context
454 : * @return 0 on success
455 : */
456 : static int
457 1 : check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg)
458 : {
459 : struct TDB_Purse purse;
460 : struct TALER_CoinPublicInfo coin;
461 : struct DepositStatus st;
462 :
463 1 : TDB_purse (pg,
464 : 10,
465 : "5",
466 : ts (1700000000),
467 : &purse);
468 1 : TDB_coin (pg,
469 : &denom,
470 : 21,
471 : &coin,
472 : NULL);
473 : /* The `purse_deposits` row goes in before the coin balance is checked,
474 : so the caller has to roll back -- which is what the /purses/.../deposit
475 : handler does. */
476 1 : FAILIF_C (GNUNET_OK !=
477 : TALER_EXCHANGEDB_start (pg,
478 : "test-purse-deposit-insufficient"),
479 : TDB_coin_free (&coin));
480 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
481 : run_deposit (pg,
482 : &purse,
483 : &coin,
484 : 21,
485 : "6",
486 : "0",
487 : &st),
488 : TALER_EXCHANGEDB_rollback (pg); TDB_coin_free (&coin));
489 1 : TALER_EXCHANGEDB_rollback (pg);
490 1 : TDB_coin_free (&coin);
491 1 : FAILIF (st.balance_ok);
492 1 : FAILIF (st.conflict);
493 1 : FAILIF (1 != TDB_count (pg,
494 : "FROM purse_deposits"));
495 1 : return 0;
496 : }
497 :
498 :
499 : /**
500 : * Once a purse has been decided, a new deposit into it is refused as too
501 : * late, whether or not the purse was merged and whether or not the
502 : * deposit would complete it. An idempotent replay of a deposit made
503 : * before the decision still succeeds.
504 : *
505 : * @param pg the database context
506 : * @return 0 on success
507 : */
508 : static int
509 1 : check_too_late (struct TALER_EXCHANGEDB_PostgresContext *pg)
510 : {
511 : struct TDB_Purse purse;
512 : struct TALER_CoinPublicInfo coin;
513 : struct TALER_ReservePublicKeyP reserve_pub;
514 : struct DepositStatus st;
515 : struct GNUNET_TIME_Absolute expiration;
516 :
517 : /* a purse that expired in 2020, merged into a reserve */
518 1 : TDB_purse (pg,
519 : 11,
520 : "5",
521 : ts (1600000000),
522 : &purse);
523 1 : TDB_reserve (pg,
524 : 11,
525 : "0",
526 : &reserve_pub);
527 : {
528 1 : char *phex = TDB_hex (&purse.purse_pub,
529 : sizeof (purse.purse_pub));
530 1 : char *rhex = TDB_hex (&reserve_pub,
531 : sizeof (reserve_pub));
532 :
533 1 : FAILIF_C (GNUNET_OK !=
534 : TDB_exec (pg,
535 : "INSERT INTO purse_merges"
536 : " (reserve_pub,purse_pub,merge_sig,merge_timestamp)"
537 : " VALUES (decode('%s','hex'),decode('%s','hex')"
538 : " ,decode(repeat('11',64),'hex'),1000000);",
539 : rhex,
540 : phex),
541 : GNUNET_free (phex); GNUNET_free (rhex));
542 1 : GNUNET_free (phex);
543 1 : GNUNET_free (rhex);
544 : }
545 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
546 : TALER_EXCHANGEDB_do_expire_purse (pg,
547 : GNUNET_TIME_UNIT_ZERO_ABS,
548 : GNUNET_TIME_absolute_get (),
549 : &expiration));
550 1 : TDB_coin (pg,
551 : &denom,
552 : 22,
553 : &coin,
554 : NULL);
555 : /* Pay in the full target amount. As with the balance check, the
556 : `purse_deposits` row is written first, so roll back. */
557 1 : FAILIF_C (GNUNET_OK !=
558 : TALER_EXCHANGEDB_start (pg,
559 : "test-purse-deposit-too-late"),
560 : TDB_coin_free (&coin));
561 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
562 : run_deposit (pg,
563 : &purse,
564 : &coin,
565 : 22,
566 : "5",
567 : "0",
568 : &st),
569 : TALER_EXCHANGEDB_rollback (pg); TDB_coin_free (&coin));
570 1 : TALER_EXCHANGEDB_rollback (pg);
571 1 : TDB_coin_free (&coin);
572 1 : FAILIF (! st.too_late);
573 1 : FAILIF (st.balance_ok);
574 1 : FAILIF (st.conflict);
575 1 : FAILIF (1 != TDB_count (pg,
576 : "FROM purse_deposits"));
577 :
578 : /* A purse that was never merged and has been decided (refunded):
579 : a deposit that would not complete it is refused as well, but a
580 : replay of a deposit made before the decision is still idempotent.
581 : All of this runs in one transaction that is rolled back, so the
582 : later checks see the table unchanged. */
583 : {
584 : struct TDB_Purse purse2;
585 : struct TALER_CoinPublicInfo coin2;
586 : struct TALER_CoinPublicInfo coin3;
587 : char *phex;
588 :
589 1 : TDB_purse (pg,
590 : 12,
591 : "5",
592 : ts (1600000000),
593 : &purse2);
594 1 : TDB_coin (pg,
595 : &denom,
596 : 23,
597 : &coin2,
598 : NULL);
599 1 : TDB_coin (pg,
600 : &denom,
601 : 24,
602 : &coin3,
603 : NULL);
604 1 : phex = TDB_hex (&purse2.purse_pub,
605 : sizeof (purse2.purse_pub));
606 1 : FAILIF_C (GNUNET_OK !=
607 : TALER_EXCHANGEDB_start (pg,
608 : "test-purse-deposit-decided"),
609 : GNUNET_free (phex);
610 : TDB_coin_free (&coin2); TDB_coin_free (&coin3));
611 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
612 : run_deposit (pg,
613 : &purse2,
614 : &coin2,
615 : 23,
616 : "1",
617 : "0",
618 : &st),
619 : TALER_EXCHANGEDB_rollback (pg); GNUNET_free (phex);
620 : TDB_coin_free (&coin2); TDB_coin_free (&coin3));
621 1 : FAILIF_C ( (! st.balance_ok) || st.too_late || st.conflict,
622 : TALER_EXCHANGEDB_rollback (pg); GNUNET_free (phex);
623 : TDB_coin_free (&coin2); TDB_coin_free (&coin3));
624 1 : FAILIF_C (GNUNET_OK !=
625 : TDB_exec (pg,
626 : "INSERT INTO purse_decision"
627 : " (purse_pub,action_timestamp,refunded)"
628 : " VALUES (decode('%s','hex'),1000000,TRUE);",
629 : phex),
630 : TALER_EXCHANGEDB_rollback (pg); GNUNET_free (phex);
631 : TDB_coin_free (&coin2); TDB_coin_free (&coin3));
632 1 : GNUNET_free (phex);
633 : /* replay of the deposit made before the decision */
634 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
635 : run_deposit (pg,
636 : &purse2,
637 : &coin2,
638 : 23,
639 : "1",
640 : "0",
641 : &st),
642 : TALER_EXCHANGEDB_rollback (pg);
643 : TDB_coin_free (&coin2); TDB_coin_free (&coin3));
644 1 : FAILIF_C ( (! st.balance_ok) || st.too_late || st.conflict,
645 : TALER_EXCHANGEDB_rollback (pg);
646 : TDB_coin_free (&coin2); TDB_coin_free (&coin3));
647 : /* a new coin, not completing the purse */
648 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
649 : run_deposit (pg,
650 : &purse2,
651 : &coin3,
652 : 24,
653 : "1",
654 : "0",
655 : &st),
656 : TALER_EXCHANGEDB_rollback (pg);
657 : TDB_coin_free (&coin2); TDB_coin_free (&coin3));
658 1 : TALER_EXCHANGEDB_rollback (pg);
659 1 : TDB_coin_free (&coin2);
660 1 : TDB_coin_free (&coin3);
661 1 : FAILIF (! st.too_late);
662 1 : FAILIF (st.balance_ok);
663 1 : FAILIF (st.conflict);
664 1 : FAILIF (1 != TDB_count (pg,
665 : "FROM purse_deposits"));
666 : }
667 1 : return 0;
668 : }
669 :
670 :
671 : /**
672 : * The iterators see the deposit; the by-purse one only for its own purse.
673 : *
674 : * @param pg the database context
675 : * @return 0 on success
676 : */
677 : static int
678 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
679 : {
680 : struct TALER_CoinSpendPublicKeyP coin_pub;
681 : struct TALER_PurseContractPublicKeyP purse_pub;
682 : struct DepositContext ctx;
683 1 : struct TALER_Amount expect = TDB_amount ("1");
684 :
685 1 : TDB_FILL (coin_pub,
686 : 20);
687 1 : TDB_FILL (purse_pub,
688 : 10);
689 1 : memset (&ctx,
690 : 0,
691 : sizeof (ctx));
692 1 : ctx.coin_pub = &coin_pub;
693 1 : FAILIF (1 !=
694 : TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id (
695 : pg,
696 : 0,
697 : &deposit_cb,
698 : &ctx));
699 1 : FAILIF (1 != ctx.matched);
700 1 : FAILIF (0 != TALER_amount_cmp (&ctx.amount,
701 : &expect));
702 : /* the purse was never merged, so there is no reserve */
703 1 : FAILIF (ctx.have_reserve);
704 :
705 1 : memset (&ctx,
706 : 0,
707 : sizeof (ctx));
708 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
709 : TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id (
710 : pg,
711 : 1000,
712 : &deposit_cb,
713 : &ctx));
714 1 : FAILIF (0 != ctx.total);
715 :
716 1 : memset (&ctx,
717 : 0,
718 : sizeof (ctx));
719 1 : ctx.coin_pub = &coin_pub;
720 1 : FAILIF (1 !=
721 : TALER_EXCHANGEDB_iterate_purse_deposits_by_purse (pg,
722 : &purse_pub,
723 : &refund_cb,
724 : &ctx));
725 1 : FAILIF (1 != ctx.matched);
726 1 : FAILIF (0 != TALER_amount_cmp (&ctx.amount,
727 : &expect));
728 :
729 : /* a purse nobody paid into has no coins */
730 : {
731 : struct TALER_PurseContractPublicKeyP other;
732 :
733 1 : TDB_FILL (other,
734 : 11);
735 1 : memset (&ctx,
736 : 0,
737 : sizeof (ctx));
738 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
739 : TALER_EXCHANGEDB_iterate_purse_deposits_by_purse (pg,
740 : &other,
741 : &refund_cb,
742 : &ctx));
743 1 : FAILIF (0 != ctx.total);
744 : }
745 1 : return 0;
746 : }
747 :
748 :
749 : /**
750 : * The checks to run, in order.
751 : */
752 : static const struct TDB_Test tests[] = {
753 : { "purse-deposits-empty",
754 : &check_empty },
755 : { "purse-deposits-deposit",
756 : &check_deposit },
757 : { "purse-deposits-insufficient-balance",
758 : &check_insufficient_balance },
759 : { "purse-deposits-too-late",
760 : &check_too_late },
761 : { "purse-deposits-iterate",
762 : &check_iterate },
763 : { NULL, NULL }
764 : };
765 :
766 :
767 : int
768 1 : main (int argc,
769 : char *const *argv)
770 : {
771 : int ret;
772 :
773 1 : ret = TDB_main (argc,
774 : argv,
775 : "test-purse-deposits",
776 : "Tests for the exchangedb `purse_deposits' table",
777 : tests);
778 1 : TDB_denom_free (&denom);
779 1 : return ret;
780 : }
781 :
782 :
783 : /* end of test_purse_deposits.c */
|