test_wire_out.c (18528B)
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 ts (uint64_t secs) 60 { 61 struct GNUNET_TIME_Absolute abs = { 62 .abs_value_us = secs * 1000LLU * 1000LLU 63 }; 64 65 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 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 struct OutContext *ctx = cls; 126 127 (void) rowid; 128 (void) date; 129 ctx->total++; 130 if ( (NULL != ctx->wtid) && 131 (0 == GNUNET_memcmp (wtid, 132 ctx->wtid)) ) 133 { 134 ctx->matched++; 135 ctx->amount = *amount; 136 GNUNET_free (ctx->payto); 137 ctx->payto = GNUNET_strdup (payto_uri.full_payto); 138 } 139 if ( (0 != ctx->stop_after) && 140 (ctx->total >= ctx->stop_after) ) 141 return GNUNET_SYSERR; 142 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 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 struct TransferContext *ctx = cls; 185 186 (void) payto_uri; 187 (void) execution_time; 188 if (0 == ctx->total++) 189 ctx->first_row = row_id; 190 ctx->value_sum += amount->value; 191 } 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 amount_cb (void *cls, 227 const struct TALER_Amount *amount, 228 struct GNUNET_TIME_Absolute date) 229 { 230 struct AmountContext *ctx = cls; 231 232 (void) date; 233 ctx->total++; 234 ctx->value_sum += amount->value; 235 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 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 struct TALER_FullPayto exchange_payto = { 261 .full_payto = (char *) "payto://x-taler-bank/localhost/exchange" 262 }; 263 struct TALER_Amount a = TDB_amount (amount); 264 265 TDB_fill (wtid, 266 sizeof (*wtid), 267 seed); 268 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 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 287 { 288 struct OutContext ctx = { 0 }; 289 struct TransferContext tctx = { 0 }; 290 struct AmountContext actx = { 0 }; 291 struct TALER_Amount zero = TDB_amount ("0"); 292 293 TDB_account (pg, 294 10, 295 &account); 296 TDB_account (pg, 297 11, 298 &other); 299 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 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 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 actx.ret = GNUNET_OK; 320 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 FAILIF (0 != ctx.total); 328 FAILIF (0 != tctx.total); 329 FAILIF (0 != actx.total); 330 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 check_insert (struct TALER_EXCHANGEDB_PostgresContext *pg) 342 { 343 struct TALER_WireTransferIdentifierRawP wtid; 344 struct OutContext ctx; 345 struct TALER_Amount expect = TDB_amount ("10"); 346 347 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 FAILIF (1 != TDB_count (pg, 356 "FROM wire_out")); 357 memset (&ctx, 358 0, 359 sizeof (ctx)); 360 ctx.wtid = &wtid; 361 FAILIF (0 >= 362 TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (pg, 363 0, 364 &wire_out_cb, 365 &ctx)); 366 FAILIF_C (1 != ctx.matched, 367 GNUNET_free (ctx.payto)); 368 FAILIF_C (0 != TALER_amount_cmp (&ctx.amount, 369 &expect), 370 GNUNET_free (ctx.payto)); 371 FAILIF_C (0 != strcmp (ctx.payto, 372 account.payto.full_payto), 373 GNUNET_free (ctx.payto)); 374 GNUNET_free (ctx.payto); 375 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 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 388 { 389 struct TALER_WireTransferIdentifierRawP w2; 390 struct TALER_WireTransferIdentifierRawP w3; 391 struct OutContext ctx; 392 393 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 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 FAILIF (3 != TDB_count (pg, 410 "FROM wire_out")); 411 412 memset (&ctx, 413 0, 414 sizeof (ctx)); 415 FAILIF (3 != 416 TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (pg, 417 0, 418 &wire_out_cb, 419 &ctx)); 420 GNUNET_free (ctx.payto); 421 memset (&ctx, 422 0, 423 sizeof (ctx)); 424 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 FAILIF (0 != ctx.total); 430 431 /* everything went out through the same exchange account */ 432 memset (&ctx, 433 0, 434 sizeof (ctx)); 435 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 GNUNET_free (ctx.payto); 443 memset (&ctx, 444 0, 445 sizeof (ctx)); 446 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 FAILIF (0 != ctx.total); 454 455 /* a callback that gives up stops the iteration */ 456 memset (&ctx, 457 0, 458 sizeof (ctx)); 459 ctx.stop_after = 1; 460 FAILIF (3 != 461 TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (pg, 462 0, 463 &wire_out_cb, 464 &ctx)); 465 FAILIF (1 != ctx.total); 466 GNUNET_free (ctx.payto); 467 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 check_aml_views (struct TALER_EXCHANGEDB_PostgresContext *pg) 479 { 480 struct TALER_Amount zero = TDB_amount ("0"); 481 struct TALER_Amount five = TDB_amount ("5"); 482 struct TransferContext ctx; 483 struct AmountContext actx; 484 uint64_t lowest; 485 486 memset (&ctx, 487 0, 488 sizeof (ctx)); 489 FAILIF (3 != 490 TALER_EXCHANGEDB_iterate_exchange_debit_transfers (pg, 491 &zero, 492 0, 493 10, 494 NULL, 495 &transfer_cb, 496 &ctx)); 497 FAILIF (10 + 2 + 5 != ctx.value_sum); 498 lowest = ctx.first_row; 499 500 memset (&ctx, 501 0, 502 sizeof (ctx)); 503 FAILIF (2 != 504 TALER_EXCHANGEDB_iterate_exchange_debit_transfers (pg, 505 &five, 506 0, 507 10, 508 NULL, 509 &transfer_cb, 510 &ctx)); 511 FAILIF (10 + 5 != ctx.value_sum); 512 513 memset (&ctx, 514 0, 515 sizeof (ctx)); 516 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 FAILIF (10 + 2 != ctx.value_sum); 526 527 /* the negative limit walks the table backwards */ 528 memset (&ctx, 529 0, 530 sizeof (ctx)); 531 FAILIF (3 != 532 TALER_EXCHANGEDB_iterate_exchange_debit_transfers (pg, 533 &zero, 534 1000, 535 -10, 536 NULL, 537 &transfer_cb, 538 &ctx)); 539 FAILIF (lowest == ctx.first_row); 540 541 /* the KYC view groups by the credited account */ 542 memset (&actx, 543 0, 544 sizeof (actx)); 545 actx.ret = GNUNET_OK; 546 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 FAILIF (10 + 2 != actx.value_sum); 554 555 memset (&actx, 556 0, 557 sizeof (actx)); 558 actx.ret = GNUNET_OK; 559 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 FAILIF (5 != actx.value_sum); 567 568 /* a limit in the future hides everything */ 569 memset (&actx, 570 0, 571 sizeof (actx)); 572 actx.ret = GNUNET_OK; 573 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 FAILIF (0 != actx.total); 581 582 /* and an aborting callback is not an error */ 583 memset (&actx, 584 0, 585 sizeof (actx)); 586 actx.ret = GNUNET_NO; 587 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 FAILIF (1 != actx.total); 595 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 main (int argc, 617 char *const *argv) 618 { 619 int ret; 620 621 ret = TDB_main (argc, 622 argv, 623 "test-wire-out", 624 "Tests for the exchangedb `wire_out' table", 625 tests); 626 TDB_account_free (&account); 627 TDB_account_free (&other); 628 return ret; 629 } 630 631 632 /* end of test_wire_out.c */