exchange

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

test_kycauths_in.c (19569B)


      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_kycauths_in.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `kycauths_in`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_do_import_credits() (for its KYC-auth half; the
     23  * reserve half is in test_reserves_in.c),
     24  * #TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account() and
     25  * #TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers().
     26  *
     27  * A KYC-auth transfer carries an account public key in its wire subject.
     28  * The import creates the `kyc_targets` and `wire_targets` rows for the
     29  * debited account on first sight and only sets `target_pub` afterwards, so
     30  * the checks import from the same account twice.  The primary key is
     31  * (account, wire reference), which is what makes a re-import a no-op.
     32  */
     33 #include "test_common.h"
     34 #include "exchange-database/begin_shard.h"
     35 #include "exchange-database/do_import_credits.h"
     36 #include "exchange-database/iterate_exchange_kycauth_transfers.h"
     37 #include "exchange-database/iterate_kycauth_in_above_serial_id_by_account.h"
     38 
     39 
     40 /**
     41  * Account the first two transfers come from.
     42  */
     43 #define PAYTO_A "payto://x-taler-bank/localhost/ka-a?receiver-name=A"
     44 
     45 /**
     46  * Account the third transfer comes from.
     47  */
     48 #define PAYTO_B "payto://x-taler-bank/localhost/ka-b?receiver-name=B"
     49 
     50 
     51 /**
     52  * Closure for #kycauth_cb().
     53  */
     54 struct AuthContext
     55 {
     56   /**
     57    * How many rows did the callback see?
     58    */
     59   unsigned int total;
     60 
     61   /**
     62    * Stop after this many rows; 0 for no limit.
     63    */
     64   unsigned int stop_after;
     65 
     66   /**
     67    * Account key we are looking for, NULL to match nothing.
     68    */
     69   const union TALER_AccountPublicKeyP *account_pub;
     70 
     71   /**
     72    * How many times did we see @e account_pub?
     73    */
     74   unsigned int matched;
     75 
     76   /**
     77    * Credit reported for @e account_pub.
     78    */
     79   struct TALER_Amount credit;
     80 
     81   /**
     82    * Sender account reported for @e account_pub, owned by this struct.
     83    */
     84   char *sender;
     85 };
     86 
     87 
     88 /**
     89  * Callback for #TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account().
     90  *
     91  * @param cls a `struct AuthContext *`
     92  * @param rowid row of the transfer
     93  * @param account_pub key the transfer associated with the account
     94  * @param credit amount that was transferred
     95  * @param sender_account_details account the money came from
     96  * @param wire_reference bank's identifier for the transfer
     97  * @param execution_date when the transfer was made
     98  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
     99  */
    100 static enum GNUNET_GenericReturnValue
    101 kycauth_cb (void *cls,
    102             uint64_t rowid,
    103             const union TALER_AccountPublicKeyP *account_pub,
    104             const struct TALER_Amount *credit,
    105             const struct TALER_FullPayto sender_account_details,
    106             uint64_t wire_reference,
    107             struct GNUNET_TIME_Timestamp execution_date)
    108 {
    109   struct AuthContext *ctx = cls;
    110 
    111   (void) rowid;
    112   (void) wire_reference;
    113   (void) execution_date;
    114   ctx->total++;
    115   if ( (NULL != ctx->account_pub) &&
    116        (0 == GNUNET_memcmp (account_pub,
    117                             ctx->account_pub)) )
    118   {
    119     ctx->matched++;
    120     ctx->credit = *credit;
    121     GNUNET_free (ctx->sender);
    122     ctx->sender = (NULL == sender_account_details.full_payto)
    123       ? NULL
    124       : GNUNET_strdup (sender_account_details.full_payto);
    125   }
    126   if ( (0 != ctx->stop_after) &&
    127        (ctx->total >= ctx->stop_after) )
    128     return GNUNET_SYSERR;
    129   return GNUNET_OK;
    130 }
    131 
    132 
    133 /**
    134  * Closure for #transfer_cb().
    135  */
    136 struct TransferContext
    137 {
    138   /**
    139    * How many transfers did the callback see?
    140    */
    141   unsigned int total;
    142 
    143   /**
    144    * Row of the first transfer seen.
    145    */
    146   uint64_t first_row;
    147 
    148   /**
    149    * Sum of the whole-unit parts of the amounts seen.
    150    */
    151   uint64_t value_sum;
    152 };
    153 
    154 
    155 /**
    156  * Callback for #TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers().
    157  *
    158  * @param cls a `struct TransferContext *`
    159  * @param row_id row of the transfer
    160  * @param payto_uri account the money came from
    161  * @param execution_time when the transfer was made
    162  * @param amount amount that was transferred
    163  */
    164 static void
    165 transfer_cb (void *cls,
    166              uint64_t row_id,
    167              const char *payto_uri,
    168              struct GNUNET_TIME_Absolute execution_time,
    169              const struct TALER_Amount *amount)
    170 {
    171   struct TransferContext *ctx = cls;
    172 
    173   (void) payto_uri;
    174   (void) execution_time;
    175   if (0 == ctx->total++)
    176     ctx->first_row = row_id;
    177   ctx->value_sum += amount->value;
    178 }
    179 
    180 
    181 /**
    182  * Import one KYC authentication transfer, advancing a work shard of its
    183  * own along with it.
    184  *
    185  * @param pg the database context
    186  * @param job_name job whose shard to open and advance
    187  * @param account_name exchange bank account the transfer arrived at
    188  * @param payto account the money came from
    189  * @param seed seed for the account public key
    190  * @param wire_reference bank's identifier for the transfer
    191  * @param amount amount that was transferred, e.g. "10"
    192  * @param[out] account_pub set to the key the transfer carried
    193  * @return transaction status of the import
    194  */
    195 static enum GNUNET_DB_QueryStatus
    196 import_one (struct TALER_EXCHANGEDB_PostgresContext *pg,
    197             const char *job_name,
    198             const char *account_name,
    199             const char *payto,
    200             uint32_t seed,
    201             uint64_t wire_reference,
    202             const char *amount,
    203             union TALER_AccountPublicKeyP *account_pub)
    204 {
    205   struct TALER_Amount balance = TDB_amount (amount);
    206   struct TALER_EXCHANGEDB_KycauthInInfo info;
    207   struct TALER_EXCHANGEDB_CreditBatch batch;
    208   uint64_t start;
    209   uint64_t end;
    210   uint64_t progress;
    211 
    212   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    213                  TALER_EXCHANGEDB_begin_shard (pg,
    214                                                job_name,
    215                                                GNUNET_TIME_UNIT_HOURS,
    216                                                1024,
    217                                                &start,
    218                                                &end,
    219                                                &progress));
    220   TDB_fill (account_pub,
    221             sizeof (*account_pub),
    222             seed);
    223   memset (&info,
    224           0,
    225           sizeof (info));
    226   info.account_pub = account_pub;
    227   info.balance = &balance;
    228   info.execution_time = GNUNET_TIME_timestamp_get ();
    229   info.sender_account_details.full_payto = (char *) payto;
    230   info.wire_reference = wire_reference;
    231   memset (&batch,
    232           0,
    233           sizeof (batch));
    234   batch.exchange_account_name = account_name;
    235   batch.kycauths = &info;
    236   batch.kycauths_length = 1;
    237   batch.job_name = job_name;
    238   batch.shard_start = start;
    239   batch.shard_end = end;
    240   batch.progress_row = end;
    241   batch.lease = GNUNET_TIME_UNIT_HOURS;
    242   return TALER_EXCHANGEDB_do_import_credits (pg,
    243                                              &batch,
    244                                              NULL);
    245 }
    246 
    247 
    248 /**
    249  * Nothing is known while the table is empty.
    250  *
    251  * @param pg the database context
    252  * @return 0 on success
    253  */
    254 static int
    255 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    256 {
    257   struct AuthContext ctx = { 0 };
    258   struct TransferContext tctx = { 0 };
    259   struct TALER_Amount zero = TDB_amount ("0");
    260 
    261   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    262           TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account (
    263             pg,
    264             "exchange-account-1",
    265             0,
    266             &kycauth_cb,
    267             &ctx));
    268   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    269           TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers (pg,
    270                                                                &zero,
    271                                                                0,
    272                                                                10,
    273                                                                NULL,
    274                                                                &transfer_cb,
    275                                                                &tctx));
    276   FAILIF (0 != ctx.total);
    277   FAILIF (0 != tctx.total);
    278   FAILIF (0 != TDB_count (pg,
    279                           "FROM kycauths_in"));
    280   return 0;
    281 }
    282 
    283 
    284 /**
    285  * Importing a transfer registers the account key and creates the account
    286  * rows; importing it again is a no-op.
    287  *
    288  * @param pg the database context
    289  * @return 0 on success
    290  */
    291 static int
    292 check_import (struct TALER_EXCHANGEDB_PostgresContext *pg)
    293 {
    294   union TALER_AccountPublicKeyP account_pub;
    295   struct AuthContext ctx;
    296   struct TALER_Amount expect = TDB_amount ("10");
    297   char *hex;
    298 
    299   FAILIF (0 >
    300           import_one (pg,
    301                       "ka-job-a",
    302                       "exchange-account-1",
    303                       PAYTO_A,
    304                       10,
    305                       1,
    306                       "10",
    307                       &account_pub));
    308   FAILIF (1 != TDB_count (pg,
    309                           "FROM kycauths_in"));
    310   /* the account is now known and carries the key from the wire subject */
    311   hex = TDB_hex (&account_pub,
    312                  sizeof (account_pub));
    313   FAILIF_C (1 != TDB_count (pg,
    314                             "FROM kyc_targets"
    315                             " WHERE target_pub=decode('%s','hex')",
    316                             hex),
    317             GNUNET_free (hex));
    318   GNUNET_free (hex);
    319   FAILIF (1 != TDB_count (pg,
    320                           "FROM wire_targets"));
    321 
    322   /* the same transfer again: the primary key is (account, wire reference) */
    323   FAILIF (0 >
    324           import_one (pg,
    325                       "ka-job-a",
    326                       "exchange-account-1",
    327                       PAYTO_A,
    328                       10,
    329                       1,
    330                       "10",
    331                       &account_pub));
    332   FAILIF (1 != TDB_count (pg,
    333                           "FROM kycauths_in"));
    334 
    335   memset (&ctx,
    336           0,
    337           sizeof (ctx));
    338   ctx.account_pub = &account_pub;
    339   FAILIF (0 >=
    340           TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account (
    341             pg,
    342             "exchange-account-1",
    343             0,
    344             &kycauth_cb,
    345             &ctx));
    346   FAILIF_C (1 != ctx.matched,
    347             GNUNET_free (ctx.sender));
    348   FAILIF_C (0 != TALER_amount_cmp (&ctx.credit,
    349                                    &expect),
    350             GNUNET_free (ctx.sender));
    351   FAILIF_C (0 != strcmp (ctx.sender,
    352                          PAYTO_A),
    353             GNUNET_free (ctx.sender));
    354   GNUNET_free (ctx.sender);
    355   return 0;
    356 }
    357 
    358 
    359 /**
    360  * With several transfers on file the by-account iterator filters by the
    361  * exchange bank account and honours an aborting callback.
    362  *
    363  * @param pg the database context
    364  * @return 0 on success
    365  */
    366 static int
    367 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
    368 {
    369   union TALER_AccountPublicKeyP a2;
    370   union TALER_AccountPublicKeyP a3;
    371   struct AuthContext ctx;
    372 
    373   /* a second transfer from the same account, replacing the key */
    374   FAILIF (0 >
    375           import_one (pg,
    376                       "ka-job-a",
    377                       "exchange-account-1",
    378                       PAYTO_A,
    379                       11,
    380                       2,
    381                       "2",
    382                       &a2));
    383   /* and one from a different account to a different exchange account */
    384   FAILIF (0 >
    385           import_one (pg,
    386                       "ka-job-b",
    387                       "exchange-account-2",
    388                       PAYTO_B,
    389                       12,
    390                       3,
    391                       "5",
    392                       &a3));
    393   FAILIF (3 != TDB_count (pg,
    394                           "FROM kycauths_in"));
    395   /* the second transfer from PAYTO_A took over its target_pub */
    396   {
    397     char *hex = TDB_hex (&a2,
    398                          sizeof (a2));
    399 
    400     FAILIF_C (1 != TDB_count (pg,
    401                               "FROM kyc_targets"
    402                               " WHERE target_pub=decode('%s','hex')",
    403                               hex),
    404               GNUNET_free (hex));
    405     GNUNET_free (hex);
    406   }
    407 
    408   memset (&ctx,
    409           0,
    410           sizeof (ctx));
    411   ctx.account_pub = &a3;
    412   FAILIF (2 !=
    413           TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account (
    414             pg,
    415             "exchange-account-1",
    416             0,
    417             &kycauth_cb,
    418             &ctx));
    419   FAILIF_C (0 != ctx.matched,
    420             GNUNET_free (ctx.sender));
    421   GNUNET_free (ctx.sender);
    422 
    423   memset (&ctx,
    424           0,
    425           sizeof (ctx));
    426   ctx.account_pub = &a3;
    427   FAILIF (1 !=
    428           TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account (
    429             pg,
    430             "exchange-account-2",
    431             0,
    432             &kycauth_cb,
    433             &ctx));
    434   FAILIF_C (1 != ctx.matched,
    435             GNUNET_free (ctx.sender));
    436   FAILIF_C (0 != strcmp (ctx.sender,
    437                          PAYTO_B),
    438             GNUNET_free (ctx.sender));
    439   GNUNET_free (ctx.sender);
    440 
    441   /* an exchange account nobody wired to */
    442   memset (&ctx,
    443           0,
    444           sizeof (ctx));
    445   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    446           TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account (
    447             pg,
    448             "exchange-account-nowhere",
    449             0,
    450             &kycauth_cb,
    451             &ctx));
    452   FAILIF (0 != ctx.total);
    453 
    454   /* the serial bound is inclusive, so a bound past the end finds nothing */
    455   memset (&ctx,
    456           0,
    457           sizeof (ctx));
    458   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    459           TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account (
    460             pg,
    461             "exchange-account-1",
    462             1000,
    463             &kycauth_cb,
    464             &ctx));
    465   FAILIF (0 != ctx.total);
    466 
    467   /* a callback that gives up stops the iteration without erroring out */
    468   memset (&ctx,
    469           0,
    470           sizeof (ctx));
    471   ctx.stop_after = 1;
    472   FAILIF (2 !=
    473           TALER_EXCHANGEDB_iterate_kycauth_in_above_serial_id_by_account (
    474             pg,
    475             "exchange-account-1",
    476             0,
    477             &kycauth_cb,
    478             &ctx));
    479   FAILIF (1 != ctx.total);
    480   GNUNET_free (ctx.sender);
    481   return 0;
    482 }
    483 
    484 
    485 /**
    486  * The AML view of the same rows filters by amount and by account, and can
    487  * walk the table backwards.
    488  *
    489  * @param pg the database context
    490  * @return 0 on success
    491  */
    492 static int
    493 check_kycauth_transfers (struct TALER_EXCHANGEDB_PostgresContext *pg)
    494 {
    495   struct TALER_NormalizedPayto npayto;
    496   struct TALER_NormalizedPaytoHashP h_payto;
    497   struct TALER_FullPayto payto_a = {
    498     .full_payto = (char *) PAYTO_A
    499   };
    500   struct TALER_Amount zero = TDB_amount ("0");
    501   struct TALER_Amount five = TDB_amount ("5");
    502   struct TALER_Amount huge = TDB_amount ("1000");
    503   struct TransferContext ctx;
    504   uint64_t lowest;
    505 
    506   memset (&ctx,
    507           0,
    508           sizeof (ctx));
    509   FAILIF (3 !=
    510           TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers (pg,
    511                                                                &zero,
    512                                                                0,
    513                                                                10,
    514                                                                NULL,
    515                                                                &transfer_cb,
    516                                                                &ctx));
    517   FAILIF (10 + 2 + 5 != ctx.value_sum);
    518   lowest = ctx.first_row;
    519 
    520   memset (&ctx,
    521           0,
    522           sizeof (ctx));
    523   FAILIF (2 !=
    524           TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers (pg,
    525                                                                &five,
    526                                                                0,
    527                                                                10,
    528                                                                NULL,
    529                                                                &transfer_cb,
    530                                                                &ctx));
    531   FAILIF (10 + 5 != ctx.value_sum);
    532 
    533   memset (&ctx,
    534           0,
    535           sizeof (ctx));
    536   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    537           TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers (pg,
    538                                                                &huge,
    539                                                                0,
    540                                                                10,
    541                                                                NULL,
    542                                                                &transfer_cb,
    543                                                                &ctx));
    544   FAILIF (0 != ctx.total);
    545 
    546   npayto = TALER_payto_normalize (payto_a);
    547   GNUNET_assert (NULL != npayto.normalized_payto);
    548   TALER_normalized_payto_hash (npayto,
    549                                &h_payto);
    550   GNUNET_free (npayto.normalized_payto);
    551   memset (&ctx,
    552           0,
    553           sizeof (ctx));
    554   FAILIF (2 !=
    555           TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers (pg,
    556                                                                &zero,
    557                                                                0,
    558                                                                10,
    559                                                                &h_payto,
    560                                                                &transfer_cb,
    561                                                                &ctx));
    562   FAILIF (10 + 2 != ctx.value_sum);
    563 
    564   memset (&ctx,
    565           0,
    566           sizeof (ctx));
    567   FAILIF (1 !=
    568           TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers (pg,
    569                                                                &zero,
    570                                                                0,
    571                                                                1,
    572                                                                NULL,
    573                                                                &transfer_cb,
    574                                                                &ctx));
    575   FAILIF (lowest != ctx.first_row);
    576 
    577   memset (&ctx,
    578           0,
    579           sizeof (ctx));
    580   FAILIF (3 !=
    581           TALER_EXCHANGEDB_iterate_exchange_kycauth_transfers (pg,
    582                                                                &zero,
    583                                                                1000,
    584                                                                -10,
    585                                                                NULL,
    586                                                                &transfer_cb,
    587                                                                &ctx));
    588   FAILIF (lowest == ctx.first_row);
    589   FAILIF (10 + 2 + 5 != ctx.value_sum);
    590   return 0;
    591 }
    592 
    593 
    594 /**
    595  * The checks to run, in order.
    596  */
    597 static const struct TDB_Test tests[] = {
    598   { "kycauths-in-empty",
    599     &check_empty },
    600   { "kycauths-in-import",
    601     &check_import },
    602   { "kycauths-in-iterate",
    603     &check_iterate },
    604   { "kycauths-in-transfers",
    605     &check_kycauth_transfers },
    606   { NULL, NULL }
    607 };
    608 
    609 
    610 int
    611 main (int argc,
    612       char *const *argv)
    613 {
    614   return TDB_main (argc,
    615                    argv,
    616                    "test-kycauths-in",
    617                    "Tests for the exchangedb `kycauths_in' table",
    618                    tests);
    619 }
    620 
    621 
    622 /* end of test_kycauths_in.c */