exchange

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

testing_api_cmd_bank_history_credit.c (20444B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2018-2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as
      7   published by the Free Software Foundation; either version 3, or
      8   (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file testing/testing_api_cmd_bank_history_credit.c
     21  * @brief command to check the /history/incoming API from the bank.
     22  * @author Marcello Stanisci
     23  */
     24 #include "taler/taler_json_lib.h"
     25 #include <gnunet/gnunet_curl_lib.h>
     26 #include "taler/taler_testing_lib.h"
     27 #include "taler/taler_fakebank_lib.h"
     28 #include "taler/taler_bank_service.h"
     29 #include "taler/taler_fakebank_lib.h"
     30 
     31 
     32 /**
     33  * Item in the transaction history, as reconstructed from the
     34  * command history.
     35  */
     36 struct History
     37 {
     38 
     39   /**
     40    * Wire details.
     41    */
     42   struct TALER_BANK_CreditDetails credit_details;
     43 
     44   /**
     45    * Serial ID of the wire transfer.
     46    */
     47   uint64_t row_id;
     48 
     49   /**
     50    * URL to free.
     51    */
     52   char *url;
     53 };
     54 
     55 
     56 /**
     57  * State for a "history" CMD.
     58  */
     59 struct HistoryState
     60 {
     61   /**
     62    * Base URL of the account offering the "history" operation.
     63    */
     64   char *account_url;
     65 
     66   /**
     67    * Reference to command defining the
     68    * first row number we want in the result.
     69    */
     70   const char *start_row_reference;
     71 
     72   /**
     73    * How many rows we want in the result, _at most_,
     74    * and ascending/descending.
     75    */
     76   long long num_results;
     77 
     78   /**
     79    * Handle to a pending "history" operation.
     80    */
     81   struct TALER_BANK_CreditHistoryHandle *hh;
     82 
     83   /**
     84    * The interpreter.
     85    */
     86   struct TALER_TESTING_Interpreter *is;
     87 
     88   /**
     89    * Authentication data for the operation.
     90    */
     91   struct TALER_BANK_AuthenticationData auth;
     92 
     93   /**
     94    * Expected number of results (= rows).
     95    */
     96   uint64_t results_obtained;
     97 
     98   /**
     99    * Set to true if the callback detects something
    100    * unexpected.
    101    */
    102   bool failed;
    103 
    104   /**
    105    * Expected history.
    106    */
    107   struct History *h;
    108 
    109   /**
    110    * Length of @e h
    111    */
    112   unsigned int total;
    113 
    114 };
    115 
    116 
    117 /**
    118  * Log which history we expected.  Called when an error occurs.
    119  *
    120  * @param h what we expected.
    121  * @param h_len number of entries in @a h.
    122  * @param off position of the mismatch.
    123  */
    124 static void
    125 print_expected (struct History *h,
    126                 unsigned int h_len,
    127                 unsigned int off)
    128 {
    129   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    130               "Transaction history (credit) mismatch at position %u/%u\n",
    131               off,
    132               h_len);
    133   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    134               "Expected history:\n");
    135   for (unsigned int i = 0; i<h_len; i++)
    136   {
    137     const struct TALER_BANK_CreditDetails *cd
    138       = &h[i].credit_details;
    139 
    140     switch (cd->type)
    141     {
    142     case TALER_BANK_CT_RESERVE:
    143       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    144                   "H(%u): %s (serial: %llu, RES: %s,"
    145                   " counterpart: %s)\n",
    146                   i,
    147                   TALER_amount2s (&cd->amount),
    148                   (unsigned long long) h[i].row_id,
    149                   TALER_B2S (&cd->details.reserve.reserve_pub),
    150                   cd->debit_account_uri.full_payto);
    151       break;
    152     case TALER_BANK_CT_KYCAUTH:
    153       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    154                   "H(%u): %s (serial: %llu, KYC: %s,"
    155                   " counterpart: %s)\n",
    156                   i,
    157                   TALER_amount2s (&cd->amount),
    158                   (unsigned long long) h[i].row_id,
    159                   TALER_B2S (&cd->details.kycauth.account_pub),
    160                   cd->debit_account_uri.full_payto);
    161       break;
    162     case TALER_BANK_CT_WAD:
    163       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    164                   "H(%u): %s (serial: %llu, WAD: %s-%s,"
    165                   " counterpart: %s)\n",
    166                   i,
    167                   TALER_amount2s (&cd->amount),
    168                   (unsigned long long) h[i].row_id,
    169                   TALER_B2S (&cd->details.wad.wad_id),
    170                   cd->details.wad.origin_exchange_url,
    171                   cd->debit_account_uri.full_payto);
    172       break;
    173     }
    174   }
    175 }
    176 
    177 
    178 /**
    179  * Closure for command_cb().
    180  */
    181 struct IteratorContext
    182 {
    183   /**
    184    * Array of history items to return.
    185    */
    186   struct History *h;
    187 
    188   /**
    189    * Set to the row ID from where on we should actually process history items,
    190    * or NULL if we should process all of them.
    191    */
    192   const uint64_t *row_id_start;
    193 
    194   /**
    195    * History state we are working on.
    196    */
    197   struct HistoryState *hs;
    198 
    199   /**
    200    * Current length of the @e h array.
    201    */
    202   unsigned int total;
    203 
    204   /**
    205    * Current write position in @e h array.
    206    */
    207   unsigned int pos;
    208 
    209   /**
    210    * Ok equals True whenever a starting row_id was provided AND was found
    211    * among the CMDs, OR no starting row was given in the first place.
    212    */
    213   bool ok;
    214 
    215 };
    216 
    217 
    218 /**
    219  * Helper function of build_history() that expands
    220  * the history for each relevant command encountered.
    221  *
    222  * @param[in,out] cls our `struct IteratorContext`
    223  * @param cmd a command to process
    224  */
    225 static void
    226 command_cb (void *cls,
    227             const struct TALER_TESTING_Command *cmd)
    228 {
    229   struct IteratorContext *ic = cls;
    230   struct HistoryState *hs = ic->hs;
    231   const uint64_t *row_id;
    232   const struct TALER_FullPayto *credit_account;
    233   const struct TALER_FullPayto *debit_account;
    234   const struct TALER_Amount *amount;
    235   const struct TALER_ReservePublicKeyP *reserve_pub;
    236   const char *exchange_credit_url;
    237 
    238   /**
    239    * The following command allows us to skip over those CMDs
    240    * that do not offer a "row_id" trait.  Such skipped CMDs are
    241    * not interesting for building a history.
    242    */
    243   if ( (GNUNET_OK !=
    244         TALER_TESTING_get_trait_bank_row (cmd,
    245                                           &row_id)) ||
    246        (GNUNET_OK !=
    247         TALER_TESTING_get_trait_credit_payto_uri (cmd,
    248                                                   &credit_account)) ||
    249        (GNUNET_OK !=
    250         TALER_TESTING_get_trait_debit_payto_uri (cmd,
    251                                                  &debit_account)) ||
    252        (GNUNET_OK !=
    253         TALER_TESTING_get_trait_amount (cmd,
    254                                         &amount)) ||
    255        (GNUNET_OK !=
    256         TALER_TESTING_get_trait_reserve_pub (cmd,
    257                                              &reserve_pub)) ||
    258        (GNUNET_OK !=
    259         TALER_TESTING_get_trait_exchange_bank_account_url (
    260           cmd,
    261           &exchange_credit_url)) )
    262     return;   // Not an interesting event
    263   // FIXME: support KYCAUTH transfer events!
    264   // FIXME-#7271: support WAD transfer events!
    265 
    266   /**
    267    * Is the interesting event a match with regard to
    268    * the row_id value?  If yes, store this condition
    269    * to the state and analyze the next CMDs.
    270    */
    271   if ( (NULL != ic->row_id_start) &&
    272        (*(ic->row_id_start) == *row_id) &&
    273        (! ic->ok) )
    274   {
    275     ic->ok = true;
    276     return;
    277   }
    278   /**
    279    * The interesting event didn't match the wanted
    280    * row_id value, analyze the next CMDs.  Note: this
    281    * branch is relevant only when row_id WAS given.
    282    */
    283   if (! ic->ok)
    284     return;
    285   if (0 != strcasecmp (hs->account_url,
    286                        exchange_credit_url))
    287     return;   // Account mismatch
    288   if (ic->total >= GNUNET_MAX (hs->num_results,
    289                                -hs->num_results) )
    290   {
    291     TALER_LOG_DEBUG ("Hit history limit\n");
    292     return;
    293   }
    294   TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
    295                   debit_account->full_payto,
    296                   credit_account->full_payto,
    297                   hs->account_url);
    298   /* found matching record, make sure we have room */
    299   if (ic->pos == ic->total)
    300     GNUNET_array_grow (ic->h,
    301                        ic->total,
    302                        ic->pos * 2);
    303   ic->h[ic->pos].url
    304     = GNUNET_strdup (debit_account->full_payto);
    305   ic->h[ic->pos].row_id
    306     = *row_id;
    307   ic->h[ic->pos].credit_details.type
    308     = TALER_BANK_CT_RESERVE;
    309   ic->h[ic->pos].credit_details.debit_account_uri.full_payto
    310     = ic->h[ic->pos].url;
    311   ic->h[ic->pos].credit_details.amount
    312     = *amount;
    313   ic->h[ic->pos].credit_details.details.reserve.reserve_pub
    314     = *reserve_pub;
    315   ic->pos++;
    316 }
    317 
    318 
    319 /**
    320  * This function constructs the list of history elements that
    321  * interest the account number of the caller.  It has two main
    322  * loops: the first to figure out how many history elements have
    323  * to be allocated, and the second to actually populate every
    324  * element.
    325  *
    326  * @param hs history state
    327  * @param[out] rh history array to initialize.
    328  * @return number of entries in @a rh.
    329  */
    330 static unsigned int
    331 build_history (struct HistoryState *hs,
    332                struct History **rh)
    333 {
    334   struct TALER_TESTING_Interpreter *is = hs->is;
    335   struct IteratorContext ic = {
    336     .hs = hs
    337   };
    338 
    339   if (NULL != hs->start_row_reference)
    340   {
    341     const struct TALER_TESTING_Command *add_incoming_cmd;
    342 
    343     TALER_LOG_INFO ("`%s': start row given via reference `%s'\n",
    344                     TALER_TESTING_interpreter_get_current_label (is),
    345                     hs->start_row_reference);
    346     add_incoming_cmd
    347       = TALER_TESTING_interpreter_lookup_command (is,
    348                                                   hs->start_row_reference);
    349     GNUNET_assert (NULL != add_incoming_cmd);
    350     GNUNET_assert (GNUNET_OK ==
    351                    TALER_TESTING_get_trait_row (add_incoming_cmd,
    352                                                 &ic.row_id_start));
    353   }
    354 
    355   ic.ok = false;
    356   if (NULL == ic.row_id_start)
    357     ic.ok = true;
    358   GNUNET_array_grow (ic.h,
    359                      ic.total,
    360                      4);
    361   GNUNET_assert (0 != hs->num_results);
    362   TALER_TESTING_iterate (is,
    363                          hs->num_results > 0,
    364                          &command_cb,
    365                          &ic);
    366   GNUNET_assert (ic.ok);
    367   GNUNET_array_grow (ic.h,
    368                      ic.total,
    369                      ic.pos);
    370   if (0 == ic.pos)
    371     TALER_LOG_DEBUG ("Empty credit history computed\n");
    372   *rh = ic.h;
    373   return ic.pos;
    374 }
    375 
    376 
    377 /**
    378  * Check that the "/history/incoming" response matches the
    379  * CMD whose offset in the list of CMDs is @a off.
    380  *
    381  * @param h expected history (array)
    382  * @param total length of @a h
    383  * @param off the offset (of the CMD list) where the command
    384  *        to check is.
    385  * @param credit_details the expected transaction details.
    386  * @return #GNUNET_OK if the transaction is what we expect.
    387  */
    388 static enum GNUNET_GenericReturnValue
    389 check_result (struct History *h,
    390               unsigned int total,
    391               unsigned int off,
    392               const struct TALER_BANK_CreditDetails *credit_details)
    393 {
    394   if (off >= total)
    395   {
    396     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    397                 "Test says history has at most %u"
    398                 " results, but got result #%u to check\n",
    399                 total,
    400                 off);
    401     print_expected (h,
    402                     total,
    403                     off);
    404     return GNUNET_SYSERR;
    405   }
    406   if ( (h[off].credit_details.type !=
    407         credit_details->type) ||
    408        (0 != TALER_amount_cmp (&h[off].credit_details.amount,
    409                                &credit_details->amount)) ||
    410        (0 != TALER_full_payto_normalize_and_cmp (
    411           h[off].credit_details.debit_account_uri,
    412           credit_details->debit_account_uri)) )
    413   {
    414     GNUNET_break (0);
    415     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    416                 "expected debit_account_uri: %s with %s\n",
    417                 h[off].credit_details.debit_account_uri.full_payto,
    418                 TALER_amount2s (&h[off].credit_details.amount));
    419     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    420                 "actual debit_account_uri: %s with %s\n",
    421                 credit_details->debit_account_uri.full_payto,
    422                 TALER_amount2s (&credit_details->amount));
    423     print_expected (h,
    424                     total,
    425                     off);
    426     return GNUNET_SYSERR;
    427   }
    428   switch (credit_details->type)
    429   {
    430   case TALER_BANK_CT_RESERVE:
    431     if (0 !=
    432         GNUNET_memcmp (&h[off].credit_details.details.reserve.reserve_pub,
    433                        &credit_details->details.reserve.reserve_pub))
    434     {
    435       GNUNET_break (0);
    436       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    437                   "expected debit_account_uri: %s with %s for %s\n",
    438                   h[off].credit_details.debit_account_uri.full_payto,
    439                   TALER_amount2s (&h[off].credit_details.amount),
    440                   TALER_B2S (&h[off].credit_details.details.reserve.reserve_pub)
    441                   );
    442       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    443                   "actual debit_account_uri: %s with %s for %s\n",
    444                   credit_details->debit_account_uri.full_payto,
    445                   TALER_amount2s (&credit_details->amount),
    446                   TALER_B2S (&credit_details->details.reserve.reserve_pub));
    447       print_expected (h,
    448                       total,
    449                       off);
    450       return GNUNET_SYSERR;
    451     }
    452     break;
    453   case TALER_BANK_CT_KYCAUTH:
    454     if (0 != GNUNET_memcmp (&h[off].credit_details.details.kycauth.account_pub,
    455                             &credit_details->details.kycauth.account_pub))
    456     {
    457       GNUNET_break (0);
    458       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    459                   "expected debit_account_uri: %s with %s for %s\n",
    460                   h[off].credit_details.debit_account_uri.full_payto,
    461                   TALER_amount2s (&h[off].credit_details.amount),
    462                   TALER_B2S (&h[off].credit_details.details.kycauth.account_pub)
    463                   );
    464       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    465                   "actual debit_account_uri: %s with %s for %s\n",
    466                   credit_details->debit_account_uri.full_payto,
    467                   TALER_amount2s (&credit_details->amount),
    468                   TALER_B2S (&credit_details->details.kycauth.account_pub));
    469       print_expected (h,
    470                       total,
    471                       off);
    472       return GNUNET_SYSERR;
    473     }
    474     break;
    475   case TALER_BANK_CT_WAD:
    476     if ( (0 != GNUNET_memcmp (&h[off].credit_details.details.wad.wad_id,
    477                               &credit_details->details.wad.wad_id)) ||
    478          (0 != strcmp (h[off].credit_details.details.wad.origin_exchange_url,
    479                        credit_details->details.wad.origin_exchange_url)) )
    480     {
    481       GNUNET_break (0);
    482       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    483                   "expected debit_account_uri: %s with %s for %s-%s\n",
    484                   h[off].credit_details.debit_account_uri.full_payto,
    485                   TALER_amount2s (&h[off].credit_details.amount),
    486                   h[off].credit_details.details.wad.origin_exchange_url,
    487                   TALER_B2S (&h[off].credit_details.details.wad.wad_id));
    488       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    489                   "actual debit_account_uri: %s with %s for %s-%s\n",
    490                   credit_details->debit_account_uri.full_payto,
    491                   TALER_amount2s (&credit_details->amount),
    492                   credit_details->details.wad.origin_exchange_url,
    493                   TALER_B2S (&credit_details->details.wad.wad_id));
    494       print_expected (h,
    495                       total,
    496                       off);
    497       return GNUNET_SYSERR;
    498     }
    499     break;
    500   }
    501   return GNUNET_OK;
    502 }
    503 
    504 
    505 /**
    506  * This callback will (1) check that the HTTP response code
    507  * is acceptable and (2) that the history is consistent.  The
    508  * consistency is checked by going through all the past CMDs,
    509  * reconstructing then the expected history as of those, and
    510  * finally check it against what the bank returned.
    511  *
    512  * @param cls closure.
    513  * @param chr http response details
    514  */
    515 static void
    516 history_cb (void *cls,
    517             const struct TALER_BANK_CreditHistoryResponse *chr)
    518 {
    519   struct HistoryState *hs = cls;
    520   struct TALER_TESTING_Interpreter *is = hs->is;
    521 
    522   hs->hh = NULL;
    523   switch (chr->http_status)
    524   {
    525   case 0:
    526     GNUNET_break (0);
    527     goto error;
    528   case MHD_HTTP_OK:
    529     for (unsigned int i = 0; i<chr->details.ok.details_length; i++)
    530     {
    531       const struct TALER_BANK_CreditDetails *cd =
    532         &chr->details.ok.details[i];
    533 
    534       /* check current element */
    535       if (GNUNET_OK !=
    536           check_result (hs->h,
    537                         hs->total,
    538                         hs->results_obtained,
    539                         cd))
    540       {
    541         GNUNET_break (0);
    542         json_dumpf (chr->response,
    543                     stderr,
    544                     JSON_COMPACT);
    545         hs->failed = true;
    546         hs->hh = NULL;
    547         TALER_TESTING_interpreter_fail (is);
    548         return;
    549       }
    550       hs->results_obtained++;
    551     }
    552     TALER_TESTING_interpreter_next (is);
    553     return;
    554   case MHD_HTTP_NO_CONTENT:
    555     if (0 == hs->total)
    556     {
    557       /* not found is OK for empty history */
    558       TALER_TESTING_interpreter_next (is);
    559       return;
    560     }
    561     GNUNET_break (0);
    562     goto error;
    563   case MHD_HTTP_NOT_FOUND:
    564     if (0 == hs->total)
    565     {
    566       /* not found is OK for empty history */
    567       TALER_TESTING_interpreter_next (is);
    568       return;
    569     }
    570     GNUNET_break (0);
    571     goto error;
    572   default:
    573     hs->hh = NULL;
    574     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    575                 "Unwanted response code from /history/incoming: %u\n",
    576                 chr->http_status);
    577     TALER_TESTING_interpreter_fail (is);
    578     return;
    579   }
    580 error:
    581   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    582               "Expected history of length %u, got %llu;"
    583               " HTTP status code: %u/%d, failed: %d\n",
    584               hs->total,
    585               (unsigned long long) hs->results_obtained,
    586               chr->http_status,
    587               (int) chr->ec,
    588               hs->failed ? 1 : 0);
    589   print_expected (hs->h,
    590                   hs->total,
    591                   UINT_MAX);
    592   TALER_TESTING_interpreter_fail (is);
    593 }
    594 
    595 
    596 /**
    597  * Run the command.
    598  *
    599  * @param cls closure.
    600  * @param cmd the command to execute.
    601  * @param is the interpreter state.
    602  */
    603 static void
    604 history_run (void *cls,
    605              const struct TALER_TESTING_Command *cmd,
    606              struct TALER_TESTING_Interpreter *is)
    607 {
    608   struct HistoryState *hs = cls;
    609   uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX;
    610   const uint64_t *row_ptr;
    611 
    612   (void) cmd;
    613   hs->is = is;
    614   /* Get row_id from trait. */
    615   if (NULL != hs->start_row_reference)
    616   {
    617     const struct TALER_TESTING_Command *history_cmd;
    618 
    619     history_cmd = TALER_TESTING_interpreter_lookup_command (
    620       is,
    621       hs->start_row_reference);
    622     if (NULL == history_cmd)
    623       TALER_TESTING_FAIL (is);
    624 
    625     if (GNUNET_OK !=
    626         TALER_TESTING_get_trait_row (history_cmd,
    627                                      &row_ptr))
    628       TALER_TESTING_FAIL (is);
    629     else
    630       row_id = *row_ptr;
    631     TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
    632                      (unsigned long long) row_id);
    633   }
    634   hs->total = build_history (hs,
    635                              &hs->h);
    636   hs->hh = TALER_BANK_credit_history (
    637     TALER_TESTING_interpreter_get_context (is),
    638     &hs->auth,
    639     row_id,
    640     hs->num_results,
    641     GNUNET_TIME_UNIT_ZERO,
    642     &history_cb,
    643     hs);
    644   GNUNET_assert (NULL != hs->hh);
    645 }
    646 
    647 
    648 /**
    649  * Free the state from a "history" CMD, and possibly cancel
    650  * a pending operation thereof.
    651  *
    652  * @param cls closure.
    653  * @param cmd the command which is being cleaned up.
    654  */
    655 static void
    656 history_cleanup (void *cls,
    657                  const struct TALER_TESTING_Command *cmd)
    658 {
    659   struct HistoryState *hs = cls;
    660 
    661   (void) cmd;
    662   if (NULL != hs->hh)
    663   {
    664     TALER_TESTING_command_incomplete (hs->is,
    665                                       cmd->label);
    666     TALER_BANK_credit_history_cancel (hs->hh);
    667   }
    668   GNUNET_free (hs->account_url);
    669   for (unsigned int off = 0; off<hs->total; off++)
    670     GNUNET_free (hs->h[off].url);
    671   GNUNET_free (hs->h);
    672   GNUNET_free (hs);
    673 }
    674 
    675 
    676 struct TALER_TESTING_Command
    677 TALER_TESTING_cmd_bank_credits (
    678   const char *label,
    679   const struct TALER_BANK_AuthenticationData *auth,
    680   const char *start_row_reference,
    681   long long num_results)
    682 {
    683   struct HistoryState *hs;
    684 
    685   hs = GNUNET_new (struct HistoryState);
    686   hs->account_url = GNUNET_strdup (auth->wire_gateway_url);
    687   hs->start_row_reference = start_row_reference;
    688   hs->num_results = num_results;
    689   hs->auth = *auth;
    690   {
    691     struct TALER_TESTING_Command cmd = {
    692       .label = label,
    693       .cls = hs,
    694       .run = &history_run,
    695       .cleanup = &history_cleanup
    696     };
    697 
    698     return cmd;
    699   }
    700 }
    701 
    702 
    703 /* end of testing_api_cmd_credit_history.c */