exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */