exchange

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

test_kyc_events.c (12742B)


      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_events.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `kyc_events`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_iterate_aml_statistics().
     23  *
     24  * `kyc_events` is a plain counter log: AML decisions may name events,
     25  * each of which becomes one row, and the statistics query groups those
     26  * rows by name over a time range.  Two of the names are special --
     27  * ACCOUNT_OPEN and ACCOUNT_IDLE also move the account's open/close time
     28  * in `kyc_targets` -- so those are checked as well.
     29  */
     30 #include "test_common.h"
     31 #include "exchange-database/insert_aml_decision.h"
     32 #include "exchange-database/insert_aml_officer.h"
     33 #include "exchange-database/iterate_aml_statistics.h"
     34 
     35 
     36 /**
     37  * One second, in microseconds.
     38  */
     39 #define SECOND 1000000LLU
     40 
     41 
     42 /**
     43  * Times at which we take our three decisions, in seconds since the
     44  * epoch.
     45  */
     46 #define T1 1600000000
     47 #define T2 1600003600
     48 #define T3 1600007200
     49 
     50 
     51 /**
     52  * Account the decisions are about.
     53  */
     54 static struct TDB_Account account;
     55 
     56 
     57 /**
     58  * Officer taking the decisions.
     59  */
     60 static struct TALER_AmlOfficerPublicKeyP officer_pub;
     61 
     62 
     63 /**
     64  * Build a timestamp from a number of seconds since the epoch.
     65  *
     66  * @param secs seconds since the epoch
     67  * @return the timestamp
     68  */
     69 static struct GNUNET_TIME_Timestamp
     70 ts (uint64_t secs)
     71 {
     72   struct GNUNET_TIME_Absolute abs = {
     73     .abs_value_us = secs * SECOND
     74   };
     75 
     76   return GNUNET_TIME_absolute_to_timestamp (abs);
     77 }
     78 
     79 
     80 /**
     81  * Closure for #statistics_cb().
     82  */
     83 struct StatsContext
     84 {
     85   /**
     86    * How many counters did the callback see?
     87    */
     88   unsigned int total;
     89 
     90   /**
     91    * Name we are interested in.
     92    */
     93   const char *name;
     94 
     95   /**
     96    * Count reported for @e name, UINT64_MAX if it was not reported.
     97    */
     98   uint64_t cnt;
     99 };
    100 
    101 
    102 /**
    103  * Callback for #TALER_EXCHANGEDB_iterate_aml_statistics().
    104  *
    105  * @param cls a `struct StatsContext *`
    106  * @param name name of the counter
    107  * @param cnt value of the counter
    108  */
    109 static void
    110 statistics_cb (void *cls,
    111                const char *name,
    112                uint64_t cnt)
    113 {
    114   struct StatsContext *ctx = cls;
    115 
    116   ctx->total++;
    117   if ( (NULL != ctx->name) &&
    118        (0 == strcmp (name,
    119                      ctx->name)) )
    120     ctx->cnt = cnt;
    121 }
    122 
    123 
    124 /**
    125  * Take an AML decision that triggers @a events.
    126  *
    127  * @param pg the database context
    128  * @param seed seed for the officer's signature
    129  * @param when when the decision is taken, in seconds since the epoch
    130  * @param num_events length of @a events
    131  * @param events the events to trigger
    132  * @return transaction status
    133  */
    134 static enum GNUNET_DB_QueryStatus
    135 decide (struct TALER_EXCHANGEDB_PostgresContext *pg,
    136         uint32_t seed,
    137         uint64_t when,
    138         size_t num_events,
    139         const char *events[static num_events])
    140 {
    141   struct TALER_AmlOfficerSignatureP decider_sig;
    142   struct TALER_FullPayto null_payto = { NULL };
    143   struct GNUNET_TIME_Timestamp last_date;
    144   json_t *new_rules;
    145   bool invalid_officer;
    146   bool unknown_account;
    147   bool is_wallet;
    148   uint64_t lms;
    149   enum GNUNET_DB_QueryStatus qs;
    150 
    151   TDB_fill (&decider_sig,
    152             sizeof (decider_sig),
    153             seed);
    154   new_rules = GNUNET_JSON_PACK (
    155     GNUNET_JSON_pack_array_steal ("rules",
    156                                   json_array ()));
    157   GNUNET_assert (NULL != new_rules);
    158   qs = TALER_EXCHANGEDB_insert_aml_decision (
    159     pg,
    160     null_payto,
    161     &account.h_normalized,
    162     ts (when),
    163     GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
    164     NULL,
    165     new_rules,
    166     false,
    167     NULL,
    168     NULL,
    169     "statistics",
    170     &officer_pub,
    171     &decider_sig,
    172     num_events,
    173     events,
    174     NULL,
    175     0,
    176     NULL,
    177     NULL,
    178     GNUNET_TIME_UNIT_ZERO_TS,
    179     &invalid_officer,
    180     &unknown_account,
    181     &last_date,
    182     &lms,
    183     &is_wallet);
    184   json_decref (new_rules);
    185   GNUNET_assert (! invalid_officer);
    186   return qs;
    187 }
    188 
    189 
    190 /**
    191  * No counters while the table is empty.
    192  *
    193  * @param pg the database context
    194  * @return 0 on success
    195  */
    196 static int
    197 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    198 {
    199   const char *names[] = { "ACCOUNT_OPEN", "sar-filed" };
    200   struct TALER_MasterSignatureP master_sig;
    201   struct GNUNET_TIME_Timestamp previous_change;
    202   struct StatsContext ctx = { 0 };
    203 
    204   TDB_account (pg,
    205                10,
    206                &account);
    207   TDB_FILL (officer_pub,
    208             20);
    209   TDB_FILL (master_sig,
    210             21);
    211   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    212                  TALER_EXCHANGEDB_insert_aml_officer (pg,
    213                                                       &officer_pub,
    214                                                       &master_sig,
    215                                                       "Alex Officer",
    216                                                       true,
    217                                                       false,
    218                                                       ts (T1 - 1),
    219                                                       &previous_change));
    220   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    221           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
    222                                                    2,
    223                                                    names,
    224                                                    ts (0),
    225                                                    ts (T3 + 1),
    226                                                    &statistics_cb,
    227                                                    &ctx));
    228   FAILIF (0 != ctx.total);
    229   return 0;
    230 }
    231 
    232 
    233 /**
    234  * Decisions record their events, and the two account-lifecycle events
    235  * also move the account's open and close time.
    236  *
    237  * @param pg the database context
    238  * @return 0 on success
    239  */
    240 static int
    241 check_record (struct TALER_EXCHANGEDB_PostgresContext *pg)
    242 {
    243   const char *open_and_sar[] = { "ACCOUNT_OPEN", "sar-filed" };
    244   const char *sar[] = { "sar-filed" };
    245   const char *idle[] = { "ACCOUNT_IDLE" };
    246   const char *none[1] = { NULL };
    247 
    248   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    249           decide (pg,
    250                   30,
    251                   T1,
    252                   2,
    253                   open_and_sar));
    254   FAILIF (2 != TDB_count (pg,
    255                           "FROM kyc_events"));
    256   FAILIF (1 != TDB_count (pg,
    257                           "FROM kyc_targets"
    258                           " WHERE open_time=%llu"
    259                           "   AND close_time IS NULL",
    260                           (unsigned long long) (T1 * SECOND)));
    261 
    262   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    263           decide (pg,
    264                   31,
    265                   T2,
    266                   1,
    267                   sar));
    268   FAILIF (3 != TDB_count (pg,
    269                           "FROM kyc_events"));
    270 
    271   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    272           decide (pg,
    273                   32,
    274                   T3,
    275                   1,
    276                   idle));
    277   FAILIF (4 != TDB_count (pg,
    278                           "FROM kyc_events"));
    279   FAILIF (1 != TDB_count (pg,
    280                           "FROM kyc_targets"
    281                           " WHERE close_time=%llu",
    282                           (unsigned long long) (T3 * SECOND)));
    283 
    284   /* a decision without events records none */
    285   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    286           decide (pg,
    287                   33,
    288                   T3 + 1,
    289                   0,
    290                   none));
    291   FAILIF (4 != TDB_count (pg,
    292                           "FROM kyc_events"));
    293   return 0;
    294 }
    295 
    296 
    297 /**
    298  * The statistics group the events by name over the requested range.
    299  *
    300  * @param pg the database context
    301  * @return 0 on success
    302  */
    303 static int
    304 check_statistics (struct TALER_EXCHANGEDB_PostgresContext *pg)
    305 {
    306   const char *all[] = { "ACCOUNT_OPEN", "ACCOUNT_IDLE", "sar-filed" };
    307   const char *sar[] = { "sar-filed" };
    308   const char *unknown[] = { "no-such-event" };
    309   struct StatsContext ctx;
    310 
    311   /* over the whole range every requested name that occurred is
    312      reported, once */
    313   memset (&ctx,
    314           0,
    315           sizeof (ctx));
    316   ctx.name = "sar-filed";
    317   ctx.cnt = UINT64_MAX;
    318   FAILIF (0 >=
    319           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
    320                                                    3,
    321                                                    all,
    322                                                    ts (0),
    323                                                    ts (T3 + 2),
    324                                                    &statistics_cb,
    325                                                    &ctx));
    326   FAILIF (3 != ctx.total);
    327   FAILIF (2 != ctx.cnt);
    328 
    329   /* a name that was not asked for is not reported, even though it
    330      happened */
    331   memset (&ctx,
    332           0,
    333           sizeof (ctx));
    334   ctx.name = "sar-filed";
    335   ctx.cnt = UINT64_MAX;
    336   FAILIF (0 >=
    337           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
    338                                                    1,
    339                                                    sar,
    340                                                    ts (0),
    341                                                    ts (T3 + 2),
    342                                                    &statistics_cb,
    343                                                    &ctx));
    344   FAILIF (1 != ctx.total);
    345   FAILIF (2 != ctx.cnt);
    346 
    347   /* the start of the range is inclusive, the end is exclusive */
    348   memset (&ctx,
    349           0,
    350           sizeof (ctx));
    351   ctx.name = "sar-filed";
    352   ctx.cnt = UINT64_MAX;
    353   FAILIF (0 >=
    354           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
    355                                                    1,
    356                                                    sar,
    357                                                    ts (T2),
    358                                                    ts (T3),
    359                                                    &statistics_cb,
    360                                                    &ctx));
    361   FAILIF (1 != ctx.total);
    362   FAILIF (1 != ctx.cnt);
    363 
    364   memset (&ctx,
    365           0,
    366           sizeof (ctx));
    367   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    368           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
    369                                                    1,
    370                                                    sar,
    371                                                    ts (T1),
    372                                                    ts (T1),
    373                                                    &statistics_cb,
    374                                                    &ctx));
    375   FAILIF (0 != ctx.total);
    376 
    377   /* a range after everything that happened is empty */
    378   memset (&ctx,
    379           0,
    380           sizeof (ctx));
    381   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    382           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
    383                                                    3,
    384                                                    all,
    385                                                    ts (T3 + 2),
    386                                                    ts (T3 + 100),
    387                                                    &statistics_cb,
    388                                                    &ctx));
    389   FAILIF (0 != ctx.total);
    390 
    391   /* and so is a name nobody ever recorded */
    392   memset (&ctx,
    393           0,
    394           sizeof (ctx));
    395   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    396           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
    397                                                    1,
    398                                                    unknown,
    399                                                    ts (0),
    400                                                    ts (T3 + 2),
    401                                                    &statistics_cb,
    402                                                    &ctx));
    403   FAILIF (0 != ctx.total);
    404   return 0;
    405 }
    406 
    407 
    408 /**
    409  * The checks to run, in order.
    410  */
    411 static const struct TDB_Test tests[] = {
    412   { "kyc-events-empty",
    413     &check_empty },
    414   { "kyc-events-record",
    415     &check_record },
    416   { "kyc-events-statistics",
    417     &check_statistics },
    418   { NULL, NULL }
    419 };
    420 
    421 
    422 int
    423 main (int argc,
    424       char *const *argv)
    425 {
    426   int ret;
    427 
    428   ret = TDB_main (argc,
    429                   argv,
    430                   "test-kyc-events",
    431                   "Tests for the exchangedb `kyc_events' table",
    432                   tests);
    433   TDB_account_free (&account);
    434   return ret;
    435 }
    436 
    437 
    438 /* end of test_kyc_events.c */