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_wire_out.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `wire_out`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_insert_wire_out(),
23 : * #TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id(),
24 : * #TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id_by_account(),
25 : * #TALER_EXCHANGEDB_iterate_exchange_debit_transfers() and
26 : * #TALER_EXCHANGEDB_iterate_aggregation_amounts_for_kyc_check().
27 : *
28 : * `wire_out` records the transfers the exchange actually made. It
29 : * references `wire_targets` for the receiver, which TDB_account() creates;
30 : * the exchange's own account is stored as a plain string.
31 : */
32 : #include "test_common.h"
33 : #include "exchange-database/insert_wire_out.h"
34 : #include "exchange-database/iterate_aggregation_amounts_for_kyc_check.h"
35 : #include "exchange-database/iterate_exchange_debit_transfers.h"
36 : #include "exchange-database/iterate_wire_outs_above_serial_id.h"
37 : #include "exchange-database/iterate_wire_outs_above_serial_id_by_account.h"
38 :
39 :
40 : /**
41 : * Account the checks pay out to.
42 : */
43 : static struct TDB_Account account;
44 :
45 :
46 : /**
47 : * A second account, to show the filters are per account.
48 : */
49 : static struct TDB_Account other;
50 :
51 :
52 : /**
53 : * Build a timestamp from a number of seconds since the epoch.
54 : *
55 : * @param secs seconds since the epoch
56 : * @return the timestamp
57 : */
58 : static struct GNUNET_TIME_Timestamp
59 3 : ts (uint64_t secs)
60 : {
61 3 : struct GNUNET_TIME_Absolute abs = {
62 3 : .abs_value_us = secs * 1000LLU * 1000LLU
63 : };
64 :
65 3 : return GNUNET_TIME_absolute_to_timestamp (abs);
66 : }
67 :
68 :
69 : /**
70 : * Closure for #wire_out_cb().
71 : */
72 : struct OutContext
73 : {
74 : /**
75 : * How many rows did the callback see?
76 : */
77 : unsigned int total;
78 :
79 : /**
80 : * Stop after this many rows; 0 for no limit.
81 : */
82 : unsigned int stop_after;
83 :
84 : /**
85 : * Wire transfer we are looking for, NULL to match nothing.
86 : */
87 : const struct TALER_WireTransferIdentifierRawP *wtid;
88 :
89 : /**
90 : * How many times did we see it?
91 : */
92 : unsigned int matched;
93 :
94 : /**
95 : * Amount reported for it.
96 : */
97 : struct TALER_Amount amount;
98 :
99 : /**
100 : * Receiver account reported for it, owned by this struct.
101 : */
102 : char *payto;
103 : };
104 :
105 :
106 : /**
107 : * Callback for the `wire_out` iterators.
108 : *
109 : * @param cls a `struct OutContext *`
110 : * @param rowid row of the transfer
111 : * @param date when it was made
112 : * @param wtid its wire transfer identifier
113 : * @param payto_uri where the money went
114 : * @param amount how much was transferred
115 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
116 : */
117 : static enum GNUNET_GenericReturnValue
118 8 : wire_out_cb (void *cls,
119 : uint64_t rowid,
120 : struct GNUNET_TIME_Timestamp date,
121 : const struct TALER_WireTransferIdentifierRawP *wtid,
122 : const struct TALER_FullPayto payto_uri,
123 : const struct TALER_Amount *amount)
124 : {
125 8 : struct OutContext *ctx = cls;
126 :
127 : (void) rowid;
128 : (void) date;
129 8 : ctx->total++;
130 8 : if ( (NULL != ctx->wtid) &&
131 1 : (0 == GNUNET_memcmp (wtid,
132 : ctx->wtid)) )
133 : {
134 1 : ctx->matched++;
135 1 : ctx->amount = *amount;
136 1 : GNUNET_free (ctx->payto);
137 1 : ctx->payto = GNUNET_strdup (payto_uri.full_payto);
138 : }
139 8 : if ( (0 != ctx->stop_after) &&
140 1 : (ctx->total >= ctx->stop_after) )
141 1 : return GNUNET_SYSERR;
142 7 : return GNUNET_OK;
143 : }
144 :
145 :
146 : /**
147 : * Closure for #transfer_cb().
148 : */
149 : struct TransferContext
150 : {
151 : /**
152 : * How many transfers did the callback see?
153 : */
154 : unsigned int total;
155 :
156 : /**
157 : * Row of the first transfer seen.
158 : */
159 : uint64_t first_row;
160 :
161 : /**
162 : * Sum of the whole-unit parts of the amounts seen.
163 : */
164 : uint64_t value_sum;
165 : };
166 :
167 :
168 : /**
169 : * Callback for #TALER_EXCHANGEDB_iterate_exchange_debit_transfers().
170 : *
171 : * @param cls a `struct TransferContext *`
172 : * @param row_id row of the transfer
173 : * @param payto_uri where the money went
174 : * @param execution_time when it was made
175 : * @param amount how much was transferred
176 : */
177 : static void
178 10 : transfer_cb (void *cls,
179 : uint64_t row_id,
180 : const char *payto_uri,
181 : struct GNUNET_TIME_Absolute execution_time,
182 : const struct TALER_Amount *amount)
183 : {
184 10 : struct TransferContext *ctx = cls;
185 :
186 : (void) payto_uri;
187 : (void) execution_time;
188 10 : if (0 == ctx->total++)
189 4 : ctx->first_row = row_id;
190 10 : ctx->value_sum += amount->value;
191 10 : }
192 :
193 :
194 : /**
195 : * Closure for #amount_cb().
196 : */
197 : struct AmountContext
198 : {
199 : /**
200 : * How many amounts did the callback see?
201 : */
202 : unsigned int total;
203 :
204 : /**
205 : * Sum of the whole-unit parts of the amounts seen.
206 : */
207 : uint64_t value_sum;
208 :
209 : /**
210 : * Return this from the callback.
211 : */
212 : enum GNUNET_GenericReturnValue ret;
213 : };
214 :
215 :
216 : /**
217 : * Callback for
218 : * #TALER_EXCHANGEDB_iterate_aggregation_amounts_for_kyc_check().
219 : *
220 : * @param cls a `struct AmountContext *`
221 : * @param amount the transferred amount
222 : * @param date when it was transferred
223 : * @return what @e ret of the closure says
224 : */
225 : static enum GNUNET_GenericReturnValue
226 4 : amount_cb (void *cls,
227 : const struct TALER_Amount *amount,
228 : struct GNUNET_TIME_Absolute date)
229 : {
230 4 : struct AmountContext *ctx = cls;
231 :
232 : (void) date;
233 4 : ctx->total++;
234 4 : ctx->value_sum += amount->value;
235 4 : return ctx->ret;
236 : }
237 :
238 :
239 : /**
240 : * Record an executed wire transfer.
241 : *
242 : * @param pg the database context
243 : * @param acc account the money went to
244 : * @param seed seed for the wire transfer identifier
245 : * @param when when the transfer was made, in seconds since the epoch
246 : * @param amount how much was transferred, e.g. "10"
247 : * @param metadata extra wire subject metadata, NULL for none
248 : * @param[out] wtid set to the wire transfer identifier used
249 : * @return transaction status
250 : */
251 : static enum GNUNET_DB_QueryStatus
252 3 : add_wire_out (struct TALER_EXCHANGEDB_PostgresContext *pg,
253 : const struct TDB_Account *acc,
254 : uint32_t seed,
255 : uint64_t when,
256 : const char *amount,
257 : const char *metadata,
258 : struct TALER_WireTransferIdentifierRawP *wtid)
259 : {
260 3 : struct TALER_FullPayto exchange_payto = {
261 : .full_payto = (char *) "payto://x-taler-bank/localhost/exchange"
262 : };
263 3 : struct TALER_Amount a = TDB_amount (amount);
264 :
265 3 : TDB_fill (wtid,
266 : sizeof (*wtid),
267 : seed);
268 3 : return TALER_EXCHANGEDB_insert_wire_out (pg,
269 : ts (when),
270 : wtid,
271 : &acc->h_full,
272 : "exchange-account-1",
273 : exchange_payto,
274 : &a,
275 : metadata);
276 : }
277 :
278 :
279 : /**
280 : * Nothing is reported while the table is empty.
281 : *
282 : * @param pg the database context
283 : * @return 0 on success
284 : */
285 : static int
286 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
287 : {
288 1 : struct OutContext ctx = { 0 };
289 1 : struct TransferContext tctx = { 0 };
290 1 : struct AmountContext actx = { 0 };
291 1 : struct TALER_Amount zero = TDB_amount ("0");
292 :
293 1 : TDB_account (pg,
294 : 10,
295 : &account);
296 1 : TDB_account (pg,
297 : 11,
298 : &other);
299 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
300 : TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (pg,
301 : 0,
302 : &wire_out_cb,
303 : &ctx));
304 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
305 : TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id_by_account (
306 : pg,
307 : "exchange-account-1",
308 : 0,
309 : &wire_out_cb,
310 : &ctx));
311 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
312 : TALER_EXCHANGEDB_iterate_exchange_debit_transfers (pg,
313 : &zero,
314 : 0,
315 : 10,
316 : NULL,
317 : &transfer_cb,
318 : &tctx));
319 1 : actx.ret = GNUNET_OK;
320 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
321 : TALER_EXCHANGEDB_iterate_aggregation_amounts_for_kyc_check (
322 : pg,
323 : &account.h_normalized,
324 : GNUNET_TIME_UNIT_ZERO_ABS,
325 : &amount_cb,
326 : &actx));
327 1 : FAILIF (0 != ctx.total);
328 1 : FAILIF (0 != tctx.total);
329 1 : FAILIF (0 != actx.total);
330 1 : return 0;
331 : }
332 :
333 :
334 : /**
335 : * A recorded transfer comes back with what it was recorded with.
336 : *
337 : * @param pg the database context
338 : * @return 0 on success
339 : */
340 : static int
341 1 : check_insert (struct TALER_EXCHANGEDB_PostgresContext *pg)
342 : {
343 : struct TALER_WireTransferIdentifierRawP wtid;
344 : struct OutContext ctx;
345 1 : struct TALER_Amount expect = TDB_amount ("10");
346 :
347 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
348 : add_wire_out (pg,
349 : &account,
350 : 10,
351 : 1600000000,
352 : "10",
353 : NULL,
354 : &wtid));
355 1 : FAILIF (1 != TDB_count (pg,
356 : "FROM wire_out"));
357 1 : memset (&ctx,
358 : 0,
359 : sizeof (ctx));
360 1 : ctx.wtid = &wtid;
361 1 : FAILIF (0 >=
362 : TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (pg,
363 : 0,
364 : &wire_out_cb,
365 : &ctx));
366 1 : FAILIF_C (1 != ctx.matched,
367 : GNUNET_free (ctx.payto));
368 1 : FAILIF_C (0 != TALER_amount_cmp (&ctx.amount,
369 : &expect),
370 : GNUNET_free (ctx.payto));
371 1 : FAILIF_C (0 != strcmp (ctx.payto,
372 : account.payto.full_payto),
373 : GNUNET_free (ctx.payto));
374 1 : GNUNET_free (ctx.payto);
375 1 : return 0;
376 : }
377 :
378 :
379 : /**
380 : * The by-account iterator filters on the exchange's own bank account, and
381 : * the serial bound and the abort return behave as documented.
382 : *
383 : * @param pg the database context
384 : * @return 0 on success
385 : */
386 : static int
387 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
388 : {
389 : struct TALER_WireTransferIdentifierRawP w2;
390 : struct TALER_WireTransferIdentifierRawP w3;
391 : struct OutContext ctx;
392 :
393 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
394 : add_wire_out (pg,
395 : &account,
396 : 11,
397 : 1600003600,
398 : "2",
399 : "invoice-42",
400 : &w2));
401 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
402 : add_wire_out (pg,
403 : &other,
404 : 12,
405 : 1600007200,
406 : "5",
407 : NULL,
408 : &w3));
409 1 : FAILIF (3 != TDB_count (pg,
410 : "FROM wire_out"));
411 :
412 1 : memset (&ctx,
413 : 0,
414 : sizeof (ctx));
415 1 : FAILIF (3 !=
416 : TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (pg,
417 : 0,
418 : &wire_out_cb,
419 : &ctx));
420 1 : GNUNET_free (ctx.payto);
421 1 : memset (&ctx,
422 : 0,
423 : sizeof (ctx));
424 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
425 : TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (pg,
426 : 1000,
427 : &wire_out_cb,
428 : &ctx));
429 1 : FAILIF (0 != ctx.total);
430 :
431 : /* everything went out through the same exchange account */
432 1 : memset (&ctx,
433 : 0,
434 : sizeof (ctx));
435 1 : FAILIF (3 !=
436 : TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id_by_account (
437 : pg,
438 : "exchange-account-1",
439 : 0,
440 : &wire_out_cb,
441 : &ctx));
442 1 : GNUNET_free (ctx.payto);
443 1 : memset (&ctx,
444 : 0,
445 : sizeof (ctx));
446 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
447 : TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id_by_account (
448 : pg,
449 : "exchange-account-2",
450 : 0,
451 : &wire_out_cb,
452 : &ctx));
453 1 : FAILIF (0 != ctx.total);
454 :
455 : /* a callback that gives up stops the iteration */
456 1 : memset (&ctx,
457 : 0,
458 : sizeof (ctx));
459 1 : ctx.stop_after = 1;
460 1 : FAILIF (3 !=
461 : TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (pg,
462 : 0,
463 : &wire_out_cb,
464 : &ctx));
465 1 : FAILIF (1 != ctx.total);
466 1 : GNUNET_free (ctx.payto);
467 1 : return 0;
468 : }
469 :
470 :
471 : /**
472 : * The AML views filter by amount and by receiving account.
473 : *
474 : * @param pg the database context
475 : * @return 0 on success
476 : */
477 : static int
478 1 : check_aml_views (struct TALER_EXCHANGEDB_PostgresContext *pg)
479 : {
480 1 : struct TALER_Amount zero = TDB_amount ("0");
481 1 : struct TALER_Amount five = TDB_amount ("5");
482 : struct TransferContext ctx;
483 : struct AmountContext actx;
484 : uint64_t lowest;
485 :
486 1 : memset (&ctx,
487 : 0,
488 : sizeof (ctx));
489 1 : FAILIF (3 !=
490 : TALER_EXCHANGEDB_iterate_exchange_debit_transfers (pg,
491 : &zero,
492 : 0,
493 : 10,
494 : NULL,
495 : &transfer_cb,
496 : &ctx));
497 1 : FAILIF (10 + 2 + 5 != ctx.value_sum);
498 1 : lowest = ctx.first_row;
499 :
500 1 : memset (&ctx,
501 : 0,
502 : sizeof (ctx));
503 1 : FAILIF (2 !=
504 : TALER_EXCHANGEDB_iterate_exchange_debit_transfers (pg,
505 : &five,
506 : 0,
507 : 10,
508 : NULL,
509 : &transfer_cb,
510 : &ctx));
511 1 : FAILIF (10 + 5 != ctx.value_sum);
512 :
513 1 : memset (&ctx,
514 : 0,
515 : sizeof (ctx));
516 1 : FAILIF (2 !=
517 : TALER_EXCHANGEDB_iterate_exchange_debit_transfers (pg,
518 : &zero,
519 : 0,
520 : 10,
521 : &account.
522 : h_normalized,
523 : &transfer_cb,
524 : &ctx));
525 1 : FAILIF (10 + 2 != ctx.value_sum);
526 :
527 : /* the negative limit walks the table backwards */
528 1 : memset (&ctx,
529 : 0,
530 : sizeof (ctx));
531 1 : FAILIF (3 !=
532 : TALER_EXCHANGEDB_iterate_exchange_debit_transfers (pg,
533 : &zero,
534 : 1000,
535 : -10,
536 : NULL,
537 : &transfer_cb,
538 : &ctx));
539 1 : FAILIF (lowest == ctx.first_row);
540 :
541 : /* the KYC view groups by the credited account */
542 1 : memset (&actx,
543 : 0,
544 : sizeof (actx));
545 1 : actx.ret = GNUNET_OK;
546 1 : FAILIF (2 !=
547 : TALER_EXCHANGEDB_iterate_aggregation_amounts_for_kyc_check (
548 : pg,
549 : &account.h_normalized,
550 : GNUNET_TIME_UNIT_ZERO_ABS,
551 : &amount_cb,
552 : &actx));
553 1 : FAILIF (10 + 2 != actx.value_sum);
554 :
555 1 : memset (&actx,
556 : 0,
557 : sizeof (actx));
558 1 : actx.ret = GNUNET_OK;
559 1 : FAILIF (1 !=
560 : TALER_EXCHANGEDB_iterate_aggregation_amounts_for_kyc_check (
561 : pg,
562 : &other.h_normalized,
563 : GNUNET_TIME_UNIT_ZERO_ABS,
564 : &amount_cb,
565 : &actx));
566 1 : FAILIF (5 != actx.value_sum);
567 :
568 : /* a limit in the future hides everything */
569 1 : memset (&actx,
570 : 0,
571 : sizeof (actx));
572 1 : actx.ret = GNUNET_OK;
573 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
574 : TALER_EXCHANGEDB_iterate_aggregation_amounts_for_kyc_check (
575 : pg,
576 : &account.h_normalized,
577 : GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS),
578 : &amount_cb,
579 : &actx));
580 1 : FAILIF (0 != actx.total);
581 :
582 : /* and an aborting callback is not an error */
583 1 : memset (&actx,
584 : 0,
585 : sizeof (actx));
586 1 : actx.ret = GNUNET_NO;
587 1 : FAILIF (0 >
588 : TALER_EXCHANGEDB_iterate_aggregation_amounts_for_kyc_check (
589 : pg,
590 : &account.h_normalized,
591 : GNUNET_TIME_UNIT_ZERO_ABS,
592 : &amount_cb,
593 : &actx));
594 1 : FAILIF (1 != actx.total);
595 1 : return 0;
596 : }
597 :
598 :
599 : /**
600 : * The checks to run, in order.
601 : */
602 : static const struct TDB_Test tests[] = {
603 : { "wire-out-empty",
604 : &check_empty },
605 : { "wire-out-insert",
606 : &check_insert },
607 : { "wire-out-iterate",
608 : &check_iterate },
609 : { "wire-out-aml-views",
610 : &check_aml_views },
611 : { NULL, NULL }
612 : };
613 :
614 :
615 : int
616 1 : main (int argc,
617 : char *const *argv)
618 : {
619 : int ret;
620 :
621 1 : ret = TDB_main (argc,
622 : argv,
623 : "test-wire-out",
624 : "Tests for the exchangedb `wire_out' table",
625 : tests);
626 1 : TDB_account_free (&account);
627 1 : TDB_account_free (&other);
628 1 : return ret;
629 : }
630 :
631 :
632 : /* end of test_wire_out.c */
|