test_kyc_targets.c (17113B)
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 * Read the access token the database generated for an account. 55 * 56 * @param pg the database context 57 * @param acc account to look up 58 * @param[out] access_token set to the account's access token 59 */ 60 static void 61 get_access_token (struct TALER_EXCHANGEDB_PostgresContext *pg, 62 const struct TDB_Account *acc, 63 struct TALER_AccountAccessTokenP *access_token) 64 { 65 struct GNUNET_PQ_QueryParam params[] = { 66 GNUNET_PQ_query_param_auto_from_type (&acc->h_normalized), 67 GNUNET_PQ_query_param_end 68 }; 69 struct GNUNET_PQ_ResultSpec rs[] = { 70 GNUNET_PQ_result_spec_auto_from_type ("access_token", 71 access_token), 72 GNUNET_PQ_result_spec_end 73 }; 74 75 GNUNET_assert (GNUNET_OK == 76 GNUNET_PQ_prepare_anon (pg->conn, 77 "SELECT access_token" 78 " FROM kyc_targets" 79 " WHERE h_normalized_payto=$1;")); 80 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 81 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 82 "", 83 params, 84 rs)); 85 } 86 87 88 /** 89 * Closure for #account_cb(). 90 */ 91 struct AccountContext 92 { 93 /** 94 * How many accounts did the callback see? 95 */ 96 unsigned int total; 97 98 /** 99 * Account we are looking for, NULL to match nothing. 100 */ 101 const struct TALER_NormalizedPaytoHashP *h_payto; 102 103 /** 104 * How many times did we see it? 105 */ 106 unsigned int matched; 107 108 /** 109 * Row of the first account seen. 110 */ 111 uint64_t first_row; 112 113 /** 114 * Was the matching account flagged for investigation? 115 */ 116 bool to_investigate; 117 118 /** 119 * Was the matching account flagged high risk? 120 */ 121 bool high_risk; 122 123 /** 124 * Was a payto URI reported for it? 125 */ 126 bool have_payto; 127 }; 128 129 130 /** 131 * Callback for #TALER_EXCHANGEDB_iterate_kyc_accounts(). 132 * 133 * @param cls a `struct AccountContext *` 134 * @param row_id row of the account 135 * @param h_payto the account 136 * @param open_time when the account was opened 137 * @param close_time when it was closed 138 * @param comments file note on the account 139 * @param high_risk whether it is a high-risk relationship 140 * @param to_investigate whether it is flagged for investigation 141 * @param payto the account's payto URI 142 */ 143 static void 144 account_cb (void *cls, 145 uint64_t row_id, 146 const struct TALER_NormalizedPaytoHashP *h_payto, 147 struct GNUNET_TIME_Timestamp open_time, 148 struct GNUNET_TIME_Timestamp close_time, 149 const char *comments, 150 bool high_risk, 151 bool to_investigate, 152 struct TALER_FullPayto payto) 153 { 154 struct AccountContext *ctx = cls; 155 156 (void) open_time; 157 (void) close_time; 158 (void) comments; 159 if (0 == ctx->total++) 160 ctx->first_row = row_id; 161 if ( (NULL != ctx->h_payto) && 162 (0 == GNUNET_memcmp (h_payto, 163 ctx->h_payto)) ) 164 { 165 ctx->matched++; 166 ctx->to_investigate = to_investigate; 167 ctx->high_risk = high_risk; 168 ctx->have_payto = (NULL != payto.full_payto); 169 } 170 } 171 172 173 /** 174 * Nothing is known about an account the exchange never saw. 175 * 176 * @param pg the database context 177 * @return 0 on success 178 */ 179 static int 180 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 181 { 182 struct TALER_NormalizedPaytoHashP h_payto; 183 struct TALER_AccountAccessTokenP access_token; 184 struct GNUNET_TIME_Absolute existing_lock; 185 struct AccountContext ctx = { 0 }; 186 uint64_t kyc_target_row; 187 bool is_wallet; 188 189 TDB_FILL (h_payto, 190 1); 191 TDB_FILL (access_token, 192 1); 193 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 194 TALER_EXCHANGEDB_get_aml_file_number (pg, 195 &h_payto, 196 &kyc_target_row, 197 &is_wallet)); 198 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 199 TALER_EXCHANGEDB_get_h_payto_by_access_token (pg, 200 &access_token, 201 &h_payto, 202 &is_wallet)); 203 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 204 TALER_EXCHANGEDB_update_to_aml_locked (pg, 205 &h_payto, 206 GNUNET_TIME_UNIT_HOURS, 207 &existing_lock)); 208 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 209 TALER_EXCHANGEDB_update_to_aml_unlocked (pg, 210 &h_payto)); 211 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 212 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 213 TALER_EXCHANGE_YNA_ALL, 214 TALER_EXCHANGE_YNA_ALL, 215 TALER_EXCHANGE_YNA_ALL, 216 0, 217 10, 218 &account_cb, 219 &ctx)); 220 FAILIF (0 != ctx.total); 221 return 0; 222 } 223 224 225 /** 226 * A known account is found by its hash and by its access token. 227 * 228 * @param pg the database context 229 * @return 0 on success 230 */ 231 static int 232 check_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg) 233 { 234 struct TALER_AccountAccessTokenP access_token; 235 struct TALER_NormalizedPaytoHashP h_payto; 236 uint64_t kyc_target_row = 0; 237 bool is_wallet = true; 238 239 TDB_account (pg, 240 10, 241 &account); 242 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 243 TALER_EXCHANGEDB_get_aml_file_number (pg, 244 &account.h_normalized, 245 &kyc_target_row, 246 &is_wallet)); 247 FAILIF (0 == kyc_target_row); 248 /* TDB_account() creates bank accounts, not wallet accounts */ 249 FAILIF (is_wallet); 250 251 get_access_token (pg, 252 &account, 253 &access_token); 254 is_wallet = true; 255 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 256 TALER_EXCHANGEDB_get_h_payto_by_access_token (pg, 257 &access_token, 258 &h_payto, 259 &is_wallet)); 260 FAILIF (0 != GNUNET_memcmp (&h_payto, 261 &account.h_normalized)); 262 FAILIF (is_wallet); 263 264 /* an access token nobody was given finds nothing */ 265 { 266 struct TALER_AccountAccessTokenP other_token; 267 268 TDB_FILL (other_token, 269 99); 270 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 271 TALER_EXCHANGEDB_get_h_payto_by_access_token (pg, 272 &other_token, 273 &h_payto, 274 &is_wallet)); 275 } 276 return 0; 277 } 278 279 280 /** 281 * Locking an account reports the lock to whoever tries next, and 282 * unlocking clears it. 283 * 284 * @param pg the database context 285 * @return 0 on success 286 */ 287 static int 288 check_lock (struct TALER_EXCHANGEDB_PostgresContext *pg) 289 { 290 struct GNUNET_TIME_Absolute existing_lock; 291 292 /* no lock yet */ 293 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 294 TALER_EXCHANGEDB_update_to_aml_locked (pg, 295 &account.h_normalized, 296 GNUNET_TIME_UNIT_HOURS, 297 &existing_lock)); 298 FAILIF (! GNUNET_TIME_absolute_is_zero (existing_lock)); 299 300 /* a second attempt is told when the first lock runs out */ 301 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 302 TALER_EXCHANGEDB_update_to_aml_locked (pg, 303 &account.h_normalized, 304 GNUNET_TIME_UNIT_HOURS, 305 &existing_lock)); 306 FAILIF (GNUNET_TIME_absolute_is_zero (existing_lock)); 307 FAILIF (GNUNET_TIME_absolute_is_past (existing_lock)); 308 309 /* unlocking clears it */ 310 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 311 TALER_EXCHANGEDB_update_to_aml_unlocked (pg, 312 &account.h_normalized)); 313 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 314 TALER_EXCHANGEDB_update_to_aml_locked (pg, 315 &account.h_normalized, 316 GNUNET_TIME_UNIT_HOURS, 317 &existing_lock)); 318 FAILIF (! GNUNET_TIME_absolute_is_zero (existing_lock)); 319 /* unlocking an account that is not locked is fine */ 320 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 321 TALER_EXCHANGEDB_update_to_aml_unlocked (pg, 322 &account.h_normalized)); 323 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 324 TALER_EXCHANGEDB_update_to_aml_unlocked (pg, 325 &account.h_normalized)); 326 return 0; 327 } 328 329 330 /** 331 * The account listing reports the accounts, with the filters applied. 332 * 333 * @param pg the database context 334 * @return 0 on success 335 */ 336 static int 337 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 338 { 339 struct AccountContext ctx; 340 uint64_t lowest; 341 342 TDB_account (pg, 343 11, 344 &other); 345 memset (&ctx, 346 0, 347 sizeof (ctx)); 348 ctx.h_payto = &account.h_normalized; 349 FAILIF (2 != 350 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 351 TALER_EXCHANGE_YNA_ALL, 352 TALER_EXCHANGE_YNA_ALL, 353 TALER_EXCHANGE_YNA_ALL, 354 0, 355 10, 356 &account_cb, 357 &ctx)); 358 FAILIF (1 != ctx.matched); 359 FAILIF (! ctx.have_payto); 360 /* no AML decision has been taken, so neither flag is set */ 361 FAILIF (ctx.to_investigate); 362 FAILIF (ctx.high_risk); 363 lowest = ctx.first_row; 364 365 /* the limit caps the result set */ 366 memset (&ctx, 367 0, 368 sizeof (ctx)); 369 FAILIF (1 != 370 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 371 TALER_EXCHANGE_YNA_ALL, 372 TALER_EXCHANGE_YNA_ALL, 373 TALER_EXCHANGE_YNA_ALL, 374 0, 375 1, 376 &account_cb, 377 &ctx)); 378 FAILIF (lowest != ctx.first_row); 379 380 /* a negative limit walks the list the other way */ 381 memset (&ctx, 382 0, 383 sizeof (ctx)); 384 FAILIF (2 != 385 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 386 TALER_EXCHANGE_YNA_ALL, 387 TALER_EXCHANGE_YNA_ALL, 388 TALER_EXCHANGE_YNA_ALL, 389 1000, 390 -10, 391 &account_cb, 392 &ctx)); 393 FAILIF (lowest == ctx.first_row); 394 395 /* nothing is under investigation... */ 396 memset (&ctx, 397 0, 398 sizeof (ctx)); 399 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 400 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 401 TALER_EXCHANGE_YNA_YES, 402 TALER_EXCHANGE_YNA_ALL, 403 TALER_EXCHANGE_YNA_ALL, 404 0, 405 10, 406 &account_cb, 407 &ctx)); 408 FAILIF (0 != ctx.total); 409 /* ...so everything comes back under "not under investigation" */ 410 memset (&ctx, 411 0, 412 sizeof (ctx)); 413 FAILIF (2 != 414 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 415 TALER_EXCHANGE_YNA_NO, 416 TALER_EXCHANGE_YNA_ALL, 417 TALER_EXCHANGE_YNA_ALL, 418 0, 419 10, 420 &account_cb, 421 &ctx)); 422 423 /* nothing is high risk either */ 424 memset (&ctx, 425 0, 426 sizeof (ctx)); 427 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 428 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 429 TALER_EXCHANGE_YNA_ALL, 430 TALER_EXCHANGE_YNA_ALL, 431 TALER_EXCHANGE_YNA_YES, 432 0, 433 10, 434 &account_cb, 435 &ctx)); 436 FAILIF (0 != ctx.total); 437 438 /* and no account has an AML outcome, which is what "open" means here */ 439 memset (&ctx, 440 0, 441 sizeof (ctx)); 442 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 443 TALER_EXCHANGEDB_iterate_kyc_accounts (pg, 444 TALER_EXCHANGE_YNA_ALL, 445 TALER_EXCHANGE_YNA_NO, 446 TALER_EXCHANGE_YNA_ALL, 447 0, 448 10, 449 &account_cb, 450 &ctx)); 451 FAILIF (0 != ctx.total); 452 return 0; 453 } 454 455 456 /** 457 * The checks to run, in order. 458 */ 459 static const struct TDB_Test tests[] = { 460 { "kyc-targets-empty", 461 &check_empty }, 462 { "kyc-targets-lookup", 463 &check_lookup }, 464 { "kyc-targets-lock", 465 &check_lock }, 466 { "kyc-targets-iterate", 467 &check_iterate }, 468 { NULL, NULL } 469 }; 470 471 472 int 473 main (int argc, 474 char *const *argv) 475 { 476 int ret; 477 478 ret = TDB_main (argc, 479 argv, 480 "test-kyc-targets", 481 "Tests for the exchangedb `kyc_targets' table", 482 tests); 483 TDB_account_free (&account); 484 TDB_account_free (&other); 485 return ret; 486 } 487 488 489 /* end of test_kyc_targets.c */