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_reserve_history.c (18520B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-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_reserve_history.c
     21  * @brief Implement the /reserve/history test command.
     22  * @author Marcello Stanisci
     23  */
     24 #include "taler/taler_json_lib.h"
     25 #include <gnunet/gnunet_curl_lib.h>
     26 struct HistoryState;
     27 #define TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE struct HistoryState
     28 #include "taler/exchange/get-reserves-RESERVE_PUB-history.h"
     29 #include "taler/taler_testing_lib.h"
     30 
     31 
     32 /**
     33  * State for a "history" CMD.
     34  */
     35 struct HistoryState
     36 {
     37 
     38   /**
     39    * Public key of the reserve being analyzed.
     40    */
     41   struct TALER_ReservePublicKeyP reserve_pub;
     42 
     43   /**
     44    * Label to the command which created the reserve to check,
     45    * needed to resort the reserve key.
     46    */
     47   const char *reserve_reference;
     48 
     49   /**
     50    * Handle to the "reserve history" operation.
     51    */
     52   struct TALER_EXCHANGE_GetReservesHistoryHandle *rsh;
     53 
     54   /**
     55    * Expected reserve balance.
     56    */
     57   const char *expected_balance;
     58 
     59   /**
     60    * Private key of the reserve being analyzed.
     61    */
     62   const struct TALER_ReservePrivateKeyP *reserve_priv;
     63 
     64   /**
     65    * Interpreter state.
     66    */
     67   struct TALER_TESTING_Interpreter *is;
     68 
     69   /**
     70    * Expected HTTP response code.
     71    */
     72   unsigned int expected_response_code;
     73 
     74 };
     75 
     76 
     77 /**
     78  * Closure for analysis_cb().
     79  */
     80 struct AnalysisContext
     81 {
     82   /**
     83    * Reserve public key we are looking at.
     84    */
     85   const struct TALER_ReservePublicKeyP *reserve_pub;
     86 
     87   /**
     88    * Length of the @e history array.
     89    */
     90   unsigned int history_length;
     91 
     92   /**
     93    * Array of history items to match.
     94    */
     95   const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
     96 
     97   /**
     98    * Array of @e history_length of matched entries.
     99    */
    100   bool *found;
    101 
    102   /**
    103    * Set to true if an entry could not be found.
    104    */
    105   bool failure;
    106 };
    107 
    108 
    109 /**
    110  * Compare @a h1 and @a h2.
    111  *
    112  * @param h1 a history entry
    113  * @param h2 a history entry
    114  * @return 0 if @a h1 and @a h2 are equal
    115  */
    116 static int
    117 history_entry_cmp (
    118   const struct TALER_EXCHANGE_ReserveHistoryEntry *h1,
    119   const struct TALER_EXCHANGE_ReserveHistoryEntry *h2)
    120 {
    121   if (h1->type != h2->type)
    122     return 1;
    123   switch (h1->type)
    124   {
    125   case TALER_EXCHANGE_RTT_CREDIT:
    126     if ( (GNUNET_OK ==
    127           TALER_amount_cmp_currency (&h1->amount,
    128                                      &h2->amount)) &&
    129          (0 ==
    130           TALER_amount_cmp (&h1->amount,
    131                             &h2->amount)) &&
    132          (0 ==
    133           TALER_full_payto_cmp (h1->details.in_details.sender_url,
    134                                 h2->details.in_details.sender_url)) &&
    135          (h1->details.in_details.wire_reference ==
    136           h2->details.in_details.wire_reference) &&
    137          (GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp,
    138                                      ==,
    139                                      h2->details.in_details.timestamp)) )
    140       return 0;
    141     return 1;
    142   case TALER_EXCHANGE_RTT_WITHDRAWAL:
    143     if ( (GNUNET_OK ==
    144           TALER_amount_cmp_currency (&h1->amount,
    145                                      &h2->amount)) &&
    146          (0 ==
    147           TALER_amount_cmp (&h1->amount,
    148                             &h2->amount)) &&
    149          (0 ==
    150           TALER_amount_cmp (&h1->details.withdraw.fee,
    151                             &h2->details.withdraw.fee)) &&
    152          (h1->details.withdraw.age_restricted ==
    153           h2->details.withdraw.age_restricted) &&
    154          ((! h1->details.withdraw.age_restricted) ||
    155           (h1->details.withdraw.max_age == h2->details.withdraw.max_age) ))
    156       return 0;
    157     return 1;
    158   case TALER_EXCHANGE_RTT_RECOUP:
    159     /* exchange_sig, exchange_pub and timestamp are NOT available
    160        from the original recoup response, hence here NOT check(able/ed) */
    161     if ( (GNUNET_OK ==
    162           TALER_amount_cmp_currency (&h1->amount,
    163                                      &h2->amount)) &&
    164          (0 ==
    165           TALER_amount_cmp (&h1->amount,
    166                             &h2->amount)) &&
    167          (0 ==
    168           GNUNET_memcmp (&h1->details.recoup_details.coin_pub,
    169                          &h2->details.recoup_details.coin_pub)) )
    170       return 0;
    171     return 1;
    172   case TALER_EXCHANGE_RTT_CLOSING:
    173     /* testing_api_cmd_exec_closer doesn't set the
    174        receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp
    175        so we cannot test for it here. but if the amount matches,
    176        that should be good enough. */
    177     if ( (GNUNET_OK ==
    178           TALER_amount_cmp_currency (&h1->amount,
    179                                      &h2->amount)) &&
    180          (0 ==
    181           TALER_amount_cmp (&h1->amount,
    182                             &h2->amount)) &&
    183          (0 ==
    184           TALER_amount_cmp (&h1->details.close_details.fee,
    185                             &h2->details.close_details.fee)) )
    186       return 0;
    187     return 1;
    188   case TALER_EXCHANGE_RTT_MERGE:
    189     if ( (GNUNET_OK ==
    190           TALER_amount_cmp_currency (&h1->amount,
    191                                      &h2->amount)) &&
    192          (0 ==
    193           TALER_amount_cmp (&h1->amount,
    194                             &h2->amount)) &&
    195          (0 ==
    196           TALER_amount_cmp (&h1->details.merge_details.purse_fee,
    197                             &h2->details.merge_details.purse_fee)) &&
    198          (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.merge_timestamp,
    199                                      ==,
    200                                      h2->details.merge_details.merge_timestamp))
    201          &&
    202          (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.purse_expiration,
    203                                      ==,
    204                                      h2->details.merge_details.purse_expiration)
    205          )
    206          &&
    207          (0 ==
    208           GNUNET_memcmp (&h1->details.merge_details.merge_pub,
    209                          &h2->details.merge_details.merge_pub)) &&
    210          (0 ==
    211           GNUNET_memcmp (&h1->details.merge_details.h_contract_terms,
    212                          &h2->details.merge_details.h_contract_terms)) &&
    213          (0 ==
    214           GNUNET_memcmp (&h1->details.merge_details.purse_pub,
    215                          &h2->details.merge_details.purse_pub)) &&
    216          (0 ==
    217           GNUNET_memcmp (&h1->details.merge_details.reserve_sig,
    218                          &h2->details.merge_details.reserve_sig)) &&
    219          (h1->details.merge_details.min_age ==
    220           h2->details.merge_details.min_age) &&
    221          (h1->details.merge_details.flags ==
    222           h2->details.merge_details.flags) )
    223       return 0;
    224     return 1;
    225   case TALER_EXCHANGE_RTT_OPEN:
    226     if ( (GNUNET_OK ==
    227           TALER_amount_cmp_currency (&h1->amount,
    228                                      &h2->amount)) &&
    229          (0 ==
    230           TALER_amount_cmp (&h1->amount,
    231                             &h2->amount)) &&
    232          (GNUNET_TIME_timestamp_cmp (
    233             h1->details.open_request.request_timestamp,
    234             ==,
    235             h2->details.open_request.request_timestamp)) &&
    236          (GNUNET_TIME_timestamp_cmp (
    237             h1->details.open_request.reserve_expiration,
    238             ==,
    239             h2->details.open_request.reserve_expiration)) &&
    240          (h1->details.open_request.purse_limit ==
    241           h2->details.open_request.purse_limit) &&
    242          (0 ==
    243           TALER_amount_cmp (&h1->details.open_request.reserve_payment,
    244                             &h2->details.open_request.reserve_payment)) &&
    245          (0 ==
    246           GNUNET_memcmp (&h1->details.open_request.reserve_sig,
    247                          &h2->details.open_request.reserve_sig)) )
    248       return 0;
    249     return 1;
    250   case TALER_EXCHANGE_RTT_CLOSE:
    251     if ( (GNUNET_OK ==
    252           TALER_amount_cmp_currency (&h1->amount,
    253                                      &h2->amount)) &&
    254          (0 ==
    255           TALER_amount_cmp (&h1->amount,
    256                             &h2->amount)) &&
    257          (GNUNET_TIME_timestamp_cmp (
    258             h1->details.close_request.request_timestamp,
    259             ==,
    260             h2->details.close_request.request_timestamp)) &&
    261          (0 ==
    262           GNUNET_memcmp (&h1->details.close_request.target_account_h_payto,
    263                          &h2->details.close_request.target_account_h_payto)) &&
    264          (0 ==
    265           GNUNET_memcmp (&h1->details.close_request.reserve_sig,
    266                          &h2->details.close_request.reserve_sig)) )
    267       return 0;
    268     return 1;
    269   }
    270   GNUNET_assert (0);
    271   return 1;
    272 }
    273 
    274 
    275 /**
    276  * Check if @a cmd changed the reserve, if so, find the
    277  * entry in our history and set the respective index in found
    278  * to true. If the entry is not found, set failure.
    279  *
    280  * @param cls our `struct AnalysisContext *`
    281  * @param cmd command to analyze for impact on history
    282  */
    283 static void
    284 analyze_command (void *cls,
    285                  const struct TALER_TESTING_Command *cmd)
    286 {
    287   struct AnalysisContext *ac = cls;
    288   const struct TALER_ReservePublicKeyP *reserve_pub = ac->reserve_pub;
    289   const struct TALER_EXCHANGE_ReserveHistoryEntry *history = ac->history;
    290   unsigned int history_length = ac->history_length;
    291   bool *found = ac->found;
    292 
    293   if (TALER_TESTING_cmd_is_batch (cmd))
    294   {
    295     struct TALER_TESTING_Command *cur;
    296     struct TALER_TESTING_Command *bcmd;
    297 
    298     cur = TALER_TESTING_cmd_batch_get_current (cmd);
    299     if (GNUNET_OK !=
    300         TALER_TESTING_get_trait_batch_cmds (cmd,
    301                                             &bcmd))
    302     {
    303       GNUNET_break (0);
    304       ac->failure = true;
    305       return;
    306     }
    307     for (unsigned int i = 0; NULL != bcmd[i].label; i++)
    308     {
    309       struct TALER_TESTING_Command *step = &bcmd[i];
    310 
    311       analyze_command (ac,
    312                        step);
    313       if (ac->failure)
    314       {
    315         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    316                     "Entry for batch step `%s' missing in reserve history\n",
    317                     step->label);
    318         return;
    319       }
    320       if (step == cur)
    321         break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
    322     }
    323     return;
    324   }
    325 
    326   {
    327     const struct TALER_ReservePublicKeyP *rp;
    328     bool matched;
    329 
    330     if (GNUNET_OK !=
    331         TALER_TESTING_get_trait_reserve_pub (cmd,
    332                                              &rp))
    333       return; /* command does nothing for reserves */
    334     if (0 !=
    335         GNUNET_memcmp (rp,
    336                        reserve_pub))
    337       return; /* command affects some _other_ reserve */
    338     for (unsigned int j = 0; true; j++)
    339     {
    340       const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
    341 
    342       if (GNUNET_OK !=
    343           TALER_TESTING_get_trait_reserve_history (cmd,
    344                                                    j,
    345                                                    &he))
    346       {
    347         /* NOTE: only for debugging... */
    348         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    349                     "Command `%s' has the reserve_pub, but lacks reserve history trait for index #%u\n",
    350                     cmd->label,
    351                     j);
    352         return; /* command does nothing for reserves */
    353       }
    354       matched = false;
    355       for (unsigned int i = 0; i < history_length; i++)
    356       {
    357         if (found[i])
    358           continue; /* already found, skip */
    359         if (0 ==
    360             history_entry_cmp (he,
    361                                &history[i]))
    362         {
    363           found[i] = true;
    364           matched = true;
    365           break;
    366         }
    367       }
    368       if (! matched)
    369       {
    370         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    371                     "Reserve history entry not found for command `%s'\n",
    372                     cmd->label);
    373         ac->failure = true;
    374         return;
    375       }
    376     }
    377   }
    378 }
    379 
    380 
    381 /**
    382  * Check that the reserve balance and HTTP response code are
    383  * both acceptable.
    384  *
    385  * @param ss closure.
    386  * @param rs HTTP response details
    387  */
    388 static void
    389 reserve_history_cb (struct HistoryState *ss,
    390                     const struct TALER_EXCHANGE_GetReservesHistoryResponse *rs)
    391 {
    392   struct TALER_TESTING_Interpreter *is = ss->is;
    393   struct TALER_Amount eb;
    394 
    395   ss->rsh = NULL;
    396   if (ss->expected_response_code != rs->hr.http_status)
    397   {
    398     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    399                 "Unexpected HTTP response code: %d in %s:%u\n",
    400                 rs->hr.http_status,
    401                 __FILE__,
    402                 __LINE__);
    403     json_dumpf (rs->hr.reply,
    404                 stderr,
    405                 0);
    406     TALER_TESTING_interpreter_fail (ss->is);
    407     return;
    408   }
    409   if (MHD_HTTP_OK != rs->hr.http_status)
    410   {
    411     TALER_TESTING_interpreter_next (is);
    412     return;
    413   }
    414   GNUNET_assert (GNUNET_OK ==
    415                  TALER_string_to_amount (ss->expected_balance,
    416                                          &eb));
    417 
    418   if (0 != TALER_amount_cmp (&eb,
    419                              &rs->details.ok.balance))
    420   {
    421     GNUNET_break (0);
    422     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    423                 "Unexpected amount in reserve: %s\n",
    424                 TALER_amount_to_string (&rs->details.ok.balance));
    425     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    426                 "Expected balance of: %s\n",
    427                 TALER_amount_to_string (&eb));
    428     TALER_TESTING_interpreter_fail (ss->is);
    429     return;
    430   }
    431   {
    432     bool found[rs->details.ok.history_len];
    433     struct AnalysisContext ac = {
    434       .reserve_pub = &ss->reserve_pub,
    435       .history = rs->details.ok.history,
    436       .history_length = rs->details.ok.history_len,
    437       .found = found
    438     };
    439 
    440     memset (found,
    441             0,
    442             sizeof (found));
    443     TALER_TESTING_iterate (is,
    444                            true,
    445                            &analyze_command,
    446                            &ac);
    447     if (ac.failure)
    448     {
    449       json_dumpf (rs->hr.reply,
    450                   stderr,
    451                   JSON_INDENT (2));
    452       TALER_TESTING_interpreter_fail (ss->is);
    453       return;
    454     }
    455     for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
    456     {
    457       if (found[i])
    458         continue;
    459       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    460                   "History entry at index %u of type %d not justified by command history\n",
    461                   i,
    462                   rs->details.ok.history[i].type);
    463       json_dumpf (rs->hr.reply,
    464                   stderr,
    465                   JSON_INDENT (2));
    466       TALER_TESTING_interpreter_fail (ss->is);
    467       return;
    468     }
    469   }
    470   TALER_TESTING_interpreter_next (is);
    471 }
    472 
    473 
    474 /**
    475  * Run the command.
    476  *
    477  * @param cls closure.
    478  * @param cmd the command being executed.
    479  * @param is the interpreter state.
    480  */
    481 static void
    482 history_run (void *cls,
    483              const struct TALER_TESTING_Command *cmd,
    484              struct TALER_TESTING_Interpreter *is)
    485 {
    486   struct HistoryState *ss = cls;
    487   const struct TALER_TESTING_Command *create_reserve;
    488 
    489   ss->is = is;
    490   create_reserve
    491     = TALER_TESTING_interpreter_lookup_command (is,
    492                                                 ss->reserve_reference);
    493   if (NULL == create_reserve)
    494   {
    495     GNUNET_break (0);
    496     TALER_TESTING_interpreter_fail (is);
    497     return;
    498   }
    499   if (GNUNET_OK !=
    500       TALER_TESTING_get_trait_reserve_priv (create_reserve,
    501                                             &ss->reserve_priv))
    502   {
    503     GNUNET_break (0);
    504     TALER_LOG_ERROR ("Failed to find reserve_priv for history query\n");
    505     TALER_TESTING_interpreter_fail (is);
    506     return;
    507   }
    508   GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
    509                                       &ss->reserve_pub.eddsa_pub);
    510   ss->rsh = TALER_EXCHANGE_get_reserves_history_create (
    511     TALER_TESTING_interpreter_get_context (is),
    512     TALER_TESTING_get_exchange_url (is),
    513     TALER_TESTING_get_keys (is),
    514     ss->reserve_priv);
    515   if (NULL == ss->rsh)
    516   {
    517     GNUNET_break (0);
    518     TALER_TESTING_interpreter_fail (is);
    519     return;
    520   }
    521   if (TALER_EC_NONE !=
    522       TALER_EXCHANGE_get_reserves_history_start (ss->rsh,
    523                                                  &reserve_history_cb,
    524                                                  ss))
    525   {
    526     GNUNET_break (0);
    527     TALER_EXCHANGE_get_reserves_history_cancel (ss->rsh);
    528     ss->rsh = NULL;
    529     TALER_TESTING_interpreter_fail (is);
    530     return;
    531   }
    532 }
    533 
    534 
    535 /**
    536  * Offer internal data from a "history" CMD, to other commands.
    537  *
    538  * @param cls closure.
    539  * @param[out] ret result.
    540  * @param trait name of the trait.
    541  * @param index index number of the object to offer.
    542  * @return #GNUNET_OK on success.
    543  */
    544 static enum GNUNET_GenericReturnValue
    545 history_traits (void *cls,
    546                 const void **ret,
    547                 const char *trait,
    548                 unsigned int index)
    549 {
    550   struct HistoryState *hs = cls;
    551   struct TALER_TESTING_Trait traits[] = {
    552     TALER_TESTING_make_trait_reserve_pub (&hs->reserve_pub),
    553     TALER_TESTING_trait_end ()
    554   };
    555 
    556   return TALER_TESTING_get_trait (traits,
    557                                   ret,
    558                                   trait,
    559                                   index);
    560 }
    561 
    562 
    563 /**
    564  * Cleanup the state from a "reserve history" CMD, and possibly
    565  * cancel a pending operation thereof.
    566  *
    567  * @param cls closure.
    568  * @param cmd the command which is being cleaned up.
    569  */
    570 static void
    571 history_cleanup (void *cls,
    572                  const struct TALER_TESTING_Command *cmd)
    573 {
    574   struct HistoryState *ss = cls;
    575 
    576   if (NULL != ss->rsh)
    577   {
    578     TALER_TESTING_command_incomplete (ss->is,
    579                                       cmd->label);
    580     TALER_EXCHANGE_get_reserves_history_cancel (ss->rsh);
    581     ss->rsh = NULL;
    582   }
    583   GNUNET_free (ss);
    584 }
    585 
    586 
    587 struct TALER_TESTING_Command
    588 TALER_TESTING_cmd_reserve_history (const char *label,
    589                                    const char *reserve_reference,
    590                                    const char *expected_balance,
    591                                    unsigned int expected_response_code)
    592 {
    593   struct HistoryState *ss;
    594 
    595   GNUNET_assert (NULL != reserve_reference);
    596   ss = GNUNET_new (struct HistoryState);
    597   ss->reserve_reference = reserve_reference;
    598   ss->expected_balance = expected_balance;
    599   ss->expected_response_code = expected_response_code;
    600   {
    601     struct TALER_TESTING_Command cmd = {
    602       .cls = ss,
    603       .label = label,
    604       .run = &history_run,
    605       .cleanup = &history_cleanup,
    606       .traits = &history_traits
    607     };
    608 
    609     return cmd;
    610   }
    611 }