test_kyc_targets.c (24130B)
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_kyc_targets.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `kyc_targets` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_get_aml_file_number(), 23 * #TALER_EXCHANGEDB_get_h_payto_by_access_token(), 24 * #TALER_EXCHANGEDB_update_to_aml_locked(), 25 * #TALER_EXCHANGEDB_update_to_aml_unlocked() and 26 * #TALER_EXCHANGEDB_iterate_kyc_accounts(). 27 * 28 * `kyc_targets` is the exchange's list of accounts it knows in a KYC 29 * sense, keyed by the hash of the *normalized* payto URI. No exported 30 * function creates a row -- the deposit and credit paths do, as a side 31 * effect -- so the checks use TDB_account() for that. The access token 32 * is generated by the database itself, so the checks read it back out. 33 */ 34 #include "test_common.h" 35 #include "exchange-database/get_aml_file_number.h" 36 #include "exchange-database/get_h_payto_by_access_token.h" 37 #include "exchange-database/iterate_kyc_accounts.h" 38 #include "exchange-database/update_to_aml_locked.h" 39 #include "exchange-database/update_to_aml_unlocked.h" 40 41 42 /** 43 * Accounts the checks work on. 44 */ 45 static struct TDB_Account account; 46 47 /** 48 * A second account. 49 */ 50 static struct TDB_Account other; 51 52 53 /** 54 * Set the AML file lifecycle timestamps for an account. 55 * 56 * @param pg database context 57 * @param acc account to update 58 * @param opened true to set an opening timestamp 59 * @param closed true to set a closing timestamp 60 */ 61 static void 62 set_file_lifecycle (struct TALER_EXCHANGEDB_PostgresContext *pg, 63 const struct TDB_Account *acc, 64 bool opened, 65 bool closed) 66 { 67 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 68 struct GNUNET_PQ_QueryParam params[] = { 69 GNUNET_PQ_query_param_auto_from_type (&acc->h_normalized), 70 opened 71 ? GNUNET_PQ_query_param_timestamp (&now) 72 : GNUNET_PQ_query_param_null (), 73 closed 74 ? GNUNET_PQ_query_param_timestamp (&now) 75 : GNUNET_PQ_query_param_null (), 76 GNUNET_PQ_query_param_end 77 }; 78 79 GNUNET_assert (GNUNET_OK == 80 GNUNET_PQ_prepare_anon (pg->conn, 81 "UPDATE kyc_targets" 82 " SET open_time=$2, close_time=$3" 83 " WHERE h_normalized_payto=$1;")); 84 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 85 GNUNET_PQ_eval_prepared_non_select (pg->conn, 86 "", 87 params)); 88 } 89 90 91 /** 92 * Read the access token the database generated for an account. 93 * 94 * @param pg the database context 95 * @param acc account to look up 96 * @param[out] access_token set to the account's access token 97 */ 98 static void 99 get_access_token (struct TALER_EXCHANGEDB_PostgresContext *pg, 100 const struct TDB_Account *acc, 101 struct TALER_AccountAccessTokenP *access_token) 102 { 103 struct GNUNET_PQ_QueryParam params[] = { 104 GNUNET_PQ_query_param_auto_from_type (&acc->h_normalized), 105 GNUNET_PQ_query_param_end 106 }; 107 struct GNUNET_PQ_ResultSpec rs[] = { 108 GNUNET_PQ_result_spec_auto_from_type ("access_token", 109 access_token), 110 GNUNET_PQ_result_spec_end 111 }; 112 113 GNUNET_assert (GNUNET_OK == 114 GNUNET_PQ_prepare_anon (pg->conn, 115 "SELECT access_token" 116 " FROM kyc_targets" 117 " WHERE h_normalized_payto=$1;")); 118 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 119 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 120 "", 121 params, 122 rs)); 123 } 124 125 126 /** 127 * Closure for #account_cb(). 128 */ 129 struct AccountContext 130 { 131 /** 132 * How many accounts did the callback see? 133 */ 134 unsigned int total; 135 136 /** 137 * Account we are looking for, NULL to match nothing. 138 */ 139 const struct TALER_NormalizedPaytoHashP *h_payto; 140 141 /** 142 * How many times did we see it? 143 */ 144 unsigned int matched; 145 146 /** 147 * Row of the first account seen. 148 */ 149 uint64_t first_row; 150 151 /** 152 * Was the matching account flagged for investigation? 153 */ 154 bool to_investigate; 155 156 /** 157 * Was the matching account flagged high risk? 158 */ 159 bool high_risk; 160 161 /** 162 * Was a payto URI reported for it? 163 */ 164 bool have_payto; 165 }; 166 167 168 /** 169 * Callback for #TALER_EXCHANGEDB_iterate_kyc_accounts(). 170 * 171 * @param cls a `struct AccountContext *` 172 * @param row_id row of the account 173 * @param h_payto the account 174 * @param open_time when the account was opened 175 * @param close_time when it was closed 176 * @param comments file note on the account 177 * @param customer_label human-readable customer label 178 * @param high_risk whether it is a high-risk relationship 179 * @param to_investigate whether it is flagged for investigation 180 * @param payto the account's payto URI 181 */ 182 static void 183 account_cb (void *cls, 184 uint64_t row_id, 185 const struct TALER_NormalizedPaytoHashP *h_payto, 186 struct GNUNET_TIME_Timestamp open_time, 187 struct GNUNET_TIME_Timestamp close_time, 188 const char *comments, 189 const char *customer_label, 190 bool high_risk, 191 bool to_investigate, 192 struct TALER_FullPayto payto) 193 { 194 struct AccountContext *ctx = cls; 195 196 (void) open_time; 197 (void) close_time; 198 (void) comments; 199 (void) customer_label; 200 if (0 == ctx->total++) 201 ctx->first_row = row_id; 202 if ( (NULL != ctx->h_payto) && 203 (0 == GNUNET_memcmp (h_payto, 204 ctx->h_payto)) ) 205 { 206 ctx->matched++; 207 ctx->to_investigate = to_investigate; 208 ctx->high_risk = high_risk; 209 ctx->have_payto = (NULL != payto.full_payto); 210 } 211 } 212 213 214 /** 215 * Nothing is known about an account the exchange never saw. 216 * 217 * @param pg the database context 218 * @return 0 on success 219 */ 220 static int 221 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 222 { 223 struct TALER_NormalizedPaytoHashP h_payto; 224 struct TALER_AccountAccessTokenP access_token; 225 struct GNUNET_TIME_Absolute existing_lock; 226 struct AccountContext ctx = { 0 }; 227 uint64_t kyc_target_row; 228 bool is_wallet; 229 230 TDB_FILL (h_payto, 231 1); 232 TDB_FILL (access_token, 233 1); 234 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 235 TALER_EXCHANGEDB_get_aml_file_number (pg, 236 &h_payto, 237 &kyc_target_row, 238 &is_wallet)); 239 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 240 TALER_EXCHANGEDB_get_h_payto_by_access_token (pg, 241 &access_token, 242 &h_payto, 243 &is_wallet)); 244 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 245 TALER_EXCHANGEDB_update_to_aml_locked (pg, 246 &h_payto, 247 GNUNET_TIME_UNIT_HOURS, 248 &existing_lock)); 249 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 250 TALER_EXCHANGEDB_update_to_aml_unlocked (pg, 251 &h_payto)); 252 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 253 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 254 NULL, 255 TALER_EXCHANGE_YNA_ALL, 256 TALER_EXCHANGE_YNA_ALL, 257 TALER_EXCHANGE_YNA_ALL, 258 0, 259 10, 260 &account_cb, 261 &ctx)); 262 FAILIF (0 != ctx.total); 263 return 0; 264 } 265 266 267 /** 268 * A known account is found by its hash and by its access token. 269 * 270 * @param pg the database context 271 * @return 0 on success 272 */ 273 static int 274 check_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg) 275 { 276 struct TALER_AccountAccessTokenP access_token; 277 struct TALER_NormalizedPaytoHashP h_payto; 278 uint64_t kyc_target_row = 0; 279 bool is_wallet = true; 280 281 TDB_account (pg, 282 10, 283 &account); 284 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 285 TALER_EXCHANGEDB_get_aml_file_number (pg, 286 &account.h_normalized, 287 &kyc_target_row, 288 &is_wallet)); 289 FAILIF (0 == kyc_target_row); 290 /* TDB_account() creates bank accounts, not wallet accounts */ 291 FAILIF (is_wallet); 292 293 get_access_token (pg, 294 &account, 295 &access_token); 296 is_wallet = true; 297 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 298 TALER_EXCHANGEDB_get_h_payto_by_access_token (pg, 299 &access_token, 300 &h_payto, 301 &is_wallet)); 302 FAILIF (0 != GNUNET_memcmp (&h_payto, 303 &account.h_normalized)); 304 FAILIF (is_wallet); 305 306 /* an access token nobody was given finds nothing */ 307 { 308 struct TALER_AccountAccessTokenP other_token; 309 310 TDB_FILL (other_token, 311 99); 312 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 313 TALER_EXCHANGEDB_get_h_payto_by_access_token (pg, 314 &other_token, 315 &h_payto, 316 &is_wallet)); 317 } 318 return 0; 319 } 320 321 322 /** 323 * Locking an account reports the lock to whoever tries next, and 324 * unlocking clears it. 325 * 326 * @param pg the database context 327 * @return 0 on success 328 */ 329 static int 330 check_lock (struct TALER_EXCHANGEDB_PostgresContext *pg) 331 { 332 struct GNUNET_TIME_Absolute existing_lock; 333 334 /* no lock yet */ 335 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 336 TALER_EXCHANGEDB_update_to_aml_locked (pg, 337 &account.h_normalized, 338 GNUNET_TIME_UNIT_HOURS, 339 &existing_lock)); 340 FAILIF (! GNUNET_TIME_absolute_is_zero (existing_lock)); 341 342 /* a second attempt is told when the first lock runs out */ 343 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 344 TALER_EXCHANGEDB_update_to_aml_locked (pg, 345 &account.h_normalized, 346 GNUNET_TIME_UNIT_HOURS, 347 &existing_lock)); 348 FAILIF (GNUNET_TIME_absolute_is_zero (existing_lock)); 349 FAILIF (GNUNET_TIME_absolute_is_past (existing_lock)); 350 351 /* unlocking clears it */ 352 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 353 TALER_EXCHANGEDB_update_to_aml_unlocked (pg, 354 &account.h_normalized)); 355 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 356 TALER_EXCHANGEDB_update_to_aml_locked (pg, 357 &account.h_normalized, 358 GNUNET_TIME_UNIT_HOURS, 359 &existing_lock)); 360 FAILIF (! GNUNET_TIME_absolute_is_zero (existing_lock)); 361 /* unlocking an account that is not locked is fine */ 362 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 363 TALER_EXCHANGEDB_update_to_aml_unlocked (pg, 364 &account.h_normalized)); 365 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 366 TALER_EXCHANGEDB_update_to_aml_unlocked (pg, 367 &account.h_normalized)); 368 return 0; 369 } 370 371 372 /** 373 * The account listing reports the accounts, with the filters applied. 374 * 375 * @param pg the database context 376 * @return 0 on success 377 */ 378 static int 379 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 380 { 381 struct AccountContext ctx; 382 struct TALER_NormalizedPaytoHashP unknown; 383 uint64_t lowest; 384 385 TDB_account (pg, 386 11, 387 &other); 388 memset (&ctx, 389 0, 390 sizeof (ctx)); 391 ctx.h_payto = &account.h_normalized; 392 FAILIF (2 != 393 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 394 NULL, 395 TALER_EXCHANGE_YNA_ALL, 396 TALER_EXCHANGE_YNA_ALL, 397 TALER_EXCHANGE_YNA_ALL, 398 0, 399 10, 400 &account_cb, 401 &ctx)); 402 FAILIF (1 != ctx.matched); 403 FAILIF (! ctx.have_payto); 404 /* no AML decision has been taken, so neither flag is set */ 405 FAILIF (ctx.to_investigate); 406 FAILIF (ctx.high_risk); 407 lowest = ctx.first_row; 408 409 /* An exact account filter returns only the requested account. */ 410 memset (&ctx, 411 0, 412 sizeof (ctx)); 413 ctx.h_payto = &account.h_normalized; 414 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 415 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 416 &account.h_normalized, 417 TALER_EXCHANGE_YNA_ALL, 418 TALER_EXCHANGE_YNA_ALL, 419 TALER_EXCHANGE_YNA_ALL, 420 0, 421 10, 422 &account_cb, 423 &ctx)); 424 FAILIF (1 != ctx.total); 425 FAILIF (1 != ctx.matched); 426 427 /* An unknown exact account filter returns no result. */ 428 TDB_FILL (unknown, 429 99); 430 memset (&ctx, 431 0, 432 sizeof (ctx)); 433 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 434 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 435 &unknown, 436 TALER_EXCHANGE_YNA_ALL, 437 TALER_EXCHANGE_YNA_ALL, 438 TALER_EXCHANGE_YNA_ALL, 439 0, 440 10, 441 &account_cb, 442 &ctx)); 443 FAILIF (0 != ctx.total); 444 445 /* the limit caps the result set */ 446 memset (&ctx, 447 0, 448 sizeof (ctx)); 449 FAILIF (1 != 450 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 451 NULL, 452 TALER_EXCHANGE_YNA_ALL, 453 TALER_EXCHANGE_YNA_ALL, 454 TALER_EXCHANGE_YNA_ALL, 455 0, 456 1, 457 &account_cb, 458 &ctx)); 459 FAILIF (lowest != ctx.first_row); 460 461 /* a negative limit walks the list the other way */ 462 memset (&ctx, 463 0, 464 sizeof (ctx)); 465 FAILIF (2 != 466 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 467 NULL, 468 TALER_EXCHANGE_YNA_ALL, 469 TALER_EXCHANGE_YNA_ALL, 470 TALER_EXCHANGE_YNA_ALL, 471 1000, 472 -10, 473 &account_cb, 474 &ctx)); 475 FAILIF (lowest == ctx.first_row); 476 477 /* nothing is under investigation... */ 478 memset (&ctx, 479 0, 480 sizeof (ctx)); 481 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 482 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 483 NULL, 484 TALER_EXCHANGE_YNA_YES, 485 TALER_EXCHANGE_YNA_ALL, 486 TALER_EXCHANGE_YNA_ALL, 487 0, 488 10, 489 &account_cb, 490 &ctx)); 491 FAILIF (0 != ctx.total); 492 /* ...so everything comes back under "not under investigation" */ 493 memset (&ctx, 494 0, 495 sizeof (ctx)); 496 FAILIF (2 != 497 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 498 NULL, 499 TALER_EXCHANGE_YNA_NO, 500 TALER_EXCHANGE_YNA_ALL, 501 TALER_EXCHANGE_YNA_ALL, 502 0, 503 10, 504 &account_cb, 505 &ctx)); 506 507 /* nothing is high risk either */ 508 memset (&ctx, 509 0, 510 sizeof (ctx)); 511 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 512 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 513 NULL, 514 TALER_EXCHANGE_YNA_ALL, 515 TALER_EXCHANGE_YNA_ALL, 516 TALER_EXCHANGE_YNA_YES, 517 0, 518 10, 519 &account_cb, 520 &ctx)); 521 FAILIF (0 != ctx.total); 522 523 /* Neither AML file has been opened yet. */ 524 memset (&ctx, 525 0, 526 sizeof (ctx)); 527 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 528 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 529 NULL, 530 TALER_EXCHANGE_YNA_ALL, 531 TALER_EXCHANGE_YNA_YES, 532 TALER_EXCHANGE_YNA_ALL, 533 0, 534 10, 535 &account_cb, 536 &ctx)); 537 memset (&ctx, 538 0, 539 sizeof (ctx)); 540 FAILIF (2 != 541 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 542 NULL, 543 TALER_EXCHANGE_YNA_ALL, 544 TALER_EXCHANGE_YNA_NO, 545 TALER_EXCHANGE_YNA_ALL, 546 0, 547 10, 548 &account_cb, 549 &ctx)); 550 551 /* Opening one AML file splits the YES and NO result sets. */ 552 set_file_lifecycle (pg, 553 &account, 554 true, 555 false); 556 memset (&ctx, 557 0, 558 sizeof (ctx)); 559 FAILIF (1 != 560 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 561 NULL, 562 TALER_EXCHANGE_YNA_ALL, 563 TALER_EXCHANGE_YNA_YES, 564 TALER_EXCHANGE_YNA_ALL, 565 0, 566 10, 567 &account_cb, 568 &ctx)); 569 memset (&ctx, 570 0, 571 sizeof (ctx)); 572 FAILIF (1 != 573 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 574 NULL, 575 TALER_EXCHANGE_YNA_ALL, 576 TALER_EXCHANGE_YNA_NO, 577 TALER_EXCHANGE_YNA_ALL, 578 0, 579 10, 580 &account_cb, 581 &ctx)); 582 583 /* Closing it moves the file back into the NO result set. */ 584 set_file_lifecycle (pg, 585 &account, 586 true, 587 true); 588 memset (&ctx, 589 0, 590 sizeof (ctx)); 591 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 592 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 593 NULL, 594 TALER_EXCHANGE_YNA_ALL, 595 TALER_EXCHANGE_YNA_YES, 596 TALER_EXCHANGE_YNA_ALL, 597 0, 598 10, 599 &account_cb, 600 &ctx)); 601 memset (&ctx, 602 0, 603 sizeof (ctx)); 604 FAILIF (2 != 605 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 606 NULL, 607 TALER_EXCHANGE_YNA_ALL, 608 TALER_EXCHANGE_YNA_NO, 609 TALER_EXCHANGE_YNA_ALL, 610 0, 611 10, 612 &account_cb, 613 &ctx)); 614 return 0; 615 } 616 617 618 /** 619 * The checks to run, in order. 620 */ 621 static const struct TDB_Test tests[] = { 622 { "kyc-targets-empty", 623 &check_empty }, 624 { "kyc-targets-lookup", 625 &check_lookup }, 626 { "kyc-targets-lock", 627 &check_lock }, 628 { "kyc-targets-iterate", 629 &check_iterate }, 630 { NULL, NULL } 631 }; 632 633 634 int 635 main (int argc, 636 char *const *argv) 637 { 638 int ret; 639 640 ret = TDB_main (argc, 641 argv, 642 "test-kyc-targets", 643 "Tests for the exchangedb `kyc_targets' table", 644 tests); 645 TDB_account_free (&account); 646 TDB_account_free (&other); 647 return ret; 648 } 649 650 651 /* end of test_kyc_targets.c */