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_recoup.c (11544B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2023 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_recoup.c
     21  * @brief Implement the /recoup test command.
     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 
     28 
     29 /**
     30  * State for a "pay back" CMD.
     31  */
     32 struct RecoupState
     33 {
     34   /**
     35    * Expected HTTP status code.
     36    */
     37   unsigned int expected_response_code;
     38 
     39   /**
     40    * Command that offers a reserve private key,
     41    * plus a coin to be paid back.
     42    */
     43   const char *coin_reference;
     44 
     45   /**
     46    * The interpreter state.
     47    */
     48   struct TALER_TESTING_Interpreter *is;
     49 
     50   /**
     51    * Handle to the ongoing operation.
     52    */
     53   struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph;
     54 
     55   /**
     56    * If the recoup filled a reserve, this is set to the reserve's public key.
     57    */
     58   struct TALER_ReservePublicKeyP reserve_pub;
     59 
     60   /**
     61    * Entry in the coin's history generated by this operation.
     62    */
     63   struct TALER_EXCHANGE_CoinHistoryEntry che;
     64 
     65   /**
     66    * Public key of the refunded coin.
     67    */
     68   struct TALER_CoinSpendPublicKeyP coin;
     69 
     70   /**
     71    * Reserve history entry, set if this recoup actually filled up a reserve.
     72    * Otherwise `reserve_history.type` will be zero.
     73    */
     74   struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
     75 
     76 };
     77 
     78 
     79 /**
     80  * Check the result of the recoup request: checks whether
     81  * the HTTP response code is good, and that the coin that
     82  * was paid back belonged to the right reserve.
     83  *
     84  * @param cls closure
     85  * @param rr response details
     86  */
     87 static void
     88 recoup_cb (void *cls,
     89            const struct TALER_EXCHANGE_PostRecoupWithdrawResponse *rr)
     90 {
     91   struct RecoupState *ps = cls;
     92   const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
     93   struct TALER_TESTING_Interpreter *is = ps->is;
     94   const struct TALER_TESTING_Command *reserve_cmd;
     95   char *cref;
     96   unsigned int idx;
     97 
     98   ps->ph = NULL;
     99   if (ps->expected_response_code != hr->http_status)
    100   {
    101     TALER_TESTING_unexpected_status (is,
    102                                      hr->http_status,
    103                                      ps->expected_response_code);
    104     return;
    105   }
    106 
    107   if (GNUNET_OK !=
    108       TALER_TESTING_parse_coin_reference (
    109         ps->coin_reference,
    110         &cref,
    111         &idx))
    112   {
    113     TALER_TESTING_interpreter_fail (is);
    114     return;
    115   }
    116   (void) idx; /* do NOT use! We ignore 'idx', must be 0 for melt! */
    117 
    118   reserve_cmd = TALER_TESTING_interpreter_lookup_command (is,
    119                                                           cref);
    120   GNUNET_free (cref);
    121 
    122   if (NULL == reserve_cmd)
    123   {
    124     GNUNET_break (0);
    125     TALER_TESTING_interpreter_fail (is);
    126     return;
    127   }
    128 
    129   switch (hr->http_status)
    130   {
    131   case MHD_HTTP_OK:
    132     /* check old_coin_pub or reserve_pub, respectively */
    133     {
    134       const struct TALER_ReservePrivateKeyP *reserve_priv;
    135 
    136       if (GNUNET_OK !=
    137           TALER_TESTING_get_trait_reserve_priv (reserve_cmd,
    138                                                 &reserve_priv))
    139       {
    140         GNUNET_break (0);
    141         TALER_TESTING_interpreter_fail (is);
    142         return;
    143       }
    144       GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
    145                                           &ps->reserve_pub.eddsa_pub);
    146       if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub,
    147                               &ps->reserve_pub))
    148       {
    149         GNUNET_break (0);
    150         TALER_TESTING_interpreter_fail (is);
    151         return;
    152       }
    153       if (GNUNET_OK ==
    154           TALER_amount_is_valid (&ps->reserve_history.amount))
    155         ps->reserve_history.type = TALER_EXCHANGE_RTT_RECOUP;
    156       /* ps->reserve_history.details.recoup_details.coin_pub; // initialized earlier */
    157       ps->che.details.recoup.reserve_pub = ps->reserve_pub;
    158     }
    159     break;
    160   case MHD_HTTP_NOT_FOUND:
    161     break;
    162   case MHD_HTTP_CONFLICT:
    163     break;
    164   default:
    165     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    166                 "Unmanaged HTTP status code %u/%d.\n",
    167                 hr->http_status,
    168                 (int) hr->ec);
    169     break;
    170   }
    171   TALER_TESTING_interpreter_next (is);
    172 }
    173 
    174 
    175 /**
    176  * Run the command.
    177  *
    178  * @param cls closure.
    179  * @param cmd the command to execute.
    180  * @param is the interpreter state.
    181  */
    182 static void
    183 recoup_run (void *cls,
    184             const struct TALER_TESTING_Command *cmd,
    185             struct TALER_TESTING_Interpreter *is)
    186 {
    187   struct RecoupState *ps = cls;
    188   const struct TALER_TESTING_Command *coin_cmd;
    189   const struct TALER_CoinSpendPrivateKeyP *coin_priv;
    190   const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
    191   const struct TALER_DenominationSignature *coin_sig;
    192   const struct TALER_WithdrawMasterSeedP *seed;
    193   const struct TALER_HashBlindedPlanchetsP *h_planchets;
    194   struct TALER_PlanchetMasterSecretP secret;
    195   char *cref;
    196   unsigned int idx;
    197   const struct TALER_ExchangeBlindingValues *ewv;
    198   struct TALER_DenominationHashP h_denom_pub;
    199 
    200   ps->is = is;
    201   if (GNUNET_OK !=
    202       TALER_TESTING_parse_coin_reference (
    203         ps->coin_reference,
    204         &cref,
    205         &idx))
    206   {
    207     TALER_TESTING_interpreter_fail (is);
    208     return;
    209   }
    210 
    211   coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
    212                                                        cref);
    213   GNUNET_free (cref);
    214 
    215   if (NULL == coin_cmd)
    216   {
    217     GNUNET_break (0);
    218     TALER_TESTING_interpreter_fail (is);
    219     return;
    220   }
    221   if (GNUNET_OK !=
    222       TALER_TESTING_get_trait_coin_priv (coin_cmd,
    223                                          idx,
    224                                          &coin_priv))
    225   {
    226     GNUNET_break (0);
    227     TALER_TESTING_interpreter_fail (is);
    228     return;
    229   }
    230   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
    231                                       &ps->coin.eddsa_pub);
    232   if (GNUNET_OK !=
    233       TALER_TESTING_get_trait_exchange_blinding_values (coin_cmd,
    234                                                         idx,
    235                                                         &ewv))
    236   {
    237     GNUNET_break (0);
    238     TALER_TESTING_interpreter_fail (is);
    239     return;
    240   }
    241   if (GNUNET_OK !=
    242       TALER_TESTING_get_trait_withdraw_seed (coin_cmd,
    243                                              &seed))
    244   {
    245     GNUNET_break (0);
    246     TALER_TESTING_interpreter_fail (is);
    247     return;
    248   }
    249   GNUNET_CRYPTO_eddsa_key_get_public (
    250     &coin_priv->eddsa_priv,
    251     &ps->reserve_history.details.recoup_details.coin_pub.eddsa_pub);
    252 
    253   if (GNUNET_OK !=
    254       TALER_TESTING_get_trait_denom_pub (coin_cmd,
    255                                          idx,
    256                                          &denom_pub))
    257   {
    258     GNUNET_break (0);
    259     TALER_TESTING_interpreter_fail (is);
    260     return;
    261   }
    262   if (GNUNET_OK !=
    263       TALER_TESTING_get_trait_denom_sig (coin_cmd,
    264                                          idx,
    265                                          &coin_sig))
    266   {
    267     GNUNET_break (0);
    268     TALER_TESTING_interpreter_fail (is);
    269     return;
    270   }
    271   if (GNUNET_OK !=
    272       TALER_TESTING_get_trait_withdraw_commitment (coin_cmd,
    273                                                    &h_planchets))
    274   {
    275     GNUNET_break (0);
    276     TALER_TESTING_interpreter_fail (is);
    277     return;
    278   }
    279   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    280               "Trying to recoup denomination '%s'\n",
    281               TALER_B2S (&denom_pub->h_key));
    282   ps->che.type = TALER_EXCHANGE_CTT_RECOUP;
    283   ps->che.amount = ps->reserve_history.amount;
    284   TALER_withdraw_expand_secrets (1,
    285                                  seed,
    286                                  &secret);
    287   TALER_planchet_blinding_secret_create (&secret,
    288                                          ewv,
    289                                          &ps->che.details.recoup.coin_bks);
    290   TALER_denom_pub_hash (&denom_pub->key,
    291                         &h_denom_pub);
    292   TALER_wallet_recoup_sign (&h_denom_pub,
    293                             &ps->che.details.recoup.coin_bks,
    294                             coin_priv,
    295                             &ps->che.details.recoup.coin_sig);
    296   ps->ph = TALER_EXCHANGE_post_recoup_withdraw_create (
    297     TALER_TESTING_interpreter_get_context (is),
    298     TALER_TESTING_get_exchange_url (is),
    299     TALER_TESTING_get_keys (is),
    300     denom_pub,
    301     coin_sig,
    302     ewv,
    303     &secret,
    304     h_planchets);
    305   GNUNET_assert (NULL != ps->ph);
    306   GNUNET_assert (TALER_EC_NONE ==
    307                  TALER_EXCHANGE_post_recoup_withdraw_start (ps->ph,
    308                                                             &recoup_cb,
    309                                                             ps));
    310 }
    311 
    312 
    313 /**
    314  * Cleanup the "recoup" CMD state, and possibly cancel
    315  * a pending operation thereof.
    316  *
    317  * @param cls closure.
    318  * @param cmd the command which is being cleaned up.
    319  */
    320 static void
    321 recoup_cleanup (void *cls,
    322                 const struct TALER_TESTING_Command *cmd)
    323 {
    324   struct RecoupState *ps = cls;
    325   if (NULL != ps->ph)
    326   {
    327     TALER_EXCHANGE_post_recoup_withdraw_cancel (ps->ph);
    328     ps->ph = NULL;
    329   }
    330   GNUNET_free (ps);
    331 }
    332 
    333 
    334 /**
    335  * Offer internal data from a "recoup" CMD state to other
    336  * commands.
    337  *
    338  * @param cls closure
    339  * @param[out] ret result (could be anything)
    340  * @param trait name of the trait
    341  * @param index index number of the object to offer.
    342  * @return #GNUNET_OK on success
    343  */
    344 static enum GNUNET_GenericReturnValue
    345 recoup_traits (void *cls,
    346                const void **ret,
    347                const char *trait,
    348                unsigned int index)
    349 {
    350   struct RecoupState *ps = cls;
    351 
    352   if (ps->reserve_history.type != TALER_EXCHANGE_RTT_RECOUP)
    353     return GNUNET_SYSERR; /* no traits */
    354   {
    355     struct TALER_TESTING_Trait traits[] = {
    356       TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub),
    357       TALER_TESTING_make_trait_reserve_history (0,
    358                                                 &ps->reserve_history),
    359       TALER_TESTING_make_trait_coin_history (0,
    360                                              &ps->che),
    361       TALER_TESTING_make_trait_coin_pub (0,
    362                                          &ps->coin),
    363       TALER_TESTING_trait_end ()
    364     };
    365 
    366     return TALER_TESTING_get_trait (traits,
    367                                     ret,
    368                                     trait,
    369                                     index);
    370   }
    371 }
    372 
    373 
    374 struct TALER_TESTING_Command
    375 TALER_TESTING_cmd_recoup (const char *label,
    376                           unsigned int expected_response_code,
    377                           const char *coin_reference,
    378                           const char *amount)
    379 {
    380   struct RecoupState *ps;
    381 
    382   ps = GNUNET_new (struct RecoupState);
    383   ps->expected_response_code = expected_response_code;
    384   ps->coin_reference = coin_reference;
    385   if (GNUNET_OK !=
    386       TALER_string_to_amount (amount,
    387                               &ps->reserve_history.amount))
    388   {
    389     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    390                 "Failed to parse amount `%s' at %s\n",
    391                 amount,
    392                 label);
    393     GNUNET_assert (0);
    394   }
    395   {
    396     struct TALER_TESTING_Command cmd = {
    397       .cls = ps,
    398       .label = label,
    399       .run = &recoup_run,
    400       .cleanup = &recoup_cleanup,
    401       .traits = &recoup_traits
    402     };
    403 
    404     return cmd;
    405   }
    406 }