exchange

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

exchange_api_post-recoup-withdraw.c (15375B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2017-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 Affero 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file lib/exchange_api_post-recoup-withdraw.c
     18  * @brief Implementation of the /recoup-withdraw request of the exchange's HTTP API
     19  * @author Christian Grothoff
     20  * @author Özgür Kesim
     21  */
     22 #include <jansson.h>
     23 #include <microhttpd.h> /* just for HTTP status codes */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_json_lib.h"
     28 #include "exchange_api_common.h"
     29 #include "exchange_api_handle.h"
     30 #include "taler/taler_signatures.h"
     31 #include "exchange_api_curl_defaults.h"
     32 
     33 
     34 /**
     35  * @brief A /recoup-withdraw handle
     36  */
     37 struct TALER_EXCHANGE_PostRecoupWithdrawHandle
     38 {
     39 
     40   /**
     41    * The base URL for this request.
     42    */
     43   char *base_url;
     44 
     45   /**
     46    * The full URL for this request, set during _start.
     47    */
     48   char *url;
     49 
     50   /**
     51    * Minor context that holds body and headers.
     52    */
     53   struct TALER_CURL_PostContext post_ctx;
     54 
     55   /**
     56    * Handle for the request.
     57    */
     58   struct GNUNET_CURL_Job *job;
     59 
     60   /**
     61    * Function to call with the result.
     62    */
     63   TALER_EXCHANGE_PostRecoupWithdrawCallback cb;
     64 
     65   /**
     66    * Closure for @a cb.
     67    */
     68   TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE *cb_cls;
     69 
     70   /**
     71    * Reference to the execution context.
     72    */
     73   struct GNUNET_CURL_Context *ctx;
     74 
     75   /**
     76    * The keys of the exchange this request handle will use.
     77    */
     78   struct TALER_EXCHANGE_Keys *keys;
     79 
     80   /**
     81    * Reserve the coins were withdrawn from.
     82    */
     83   struct TALER_ReservePublicKeyP reserve_pub;
     84 
     85   /**
     86    * Commitment of the withdraw operation.
     87    */
     88   struct TALER_HashBlindedPlanchetsP planchets_h;
     89 
     90   /**
     91    * Public keys of the recouped coins, in request order.
     92    */
     93   struct TALER_CoinSpendPublicKeyP *recouped_pubs;
     94 
     95   /**
     96    * Denomination and age commitment of the recouped coins,
     97    * parallel to @e recouped_pubs.
     98    */
     99   struct TALER_EXCHANGE_RecoupedCoinInfo_ *recouped_infos;
    100 
    101   /**
    102    * Number of entries in @e recouped_pubs.
    103    */
    104   size_t num_recouped;
    105 
    106   /**
    107    * Pre-built request body.
    108    */
    109   json_t *body;
    110 
    111 };
    112 
    113 
    114 /**
    115  * Parse and verify a successful response.  If it is valid,
    116  * call the callback.
    117  *
    118  * @param prwh request handle
    119  * @param json json reply with the signature
    120  * @return #GNUNET_OK if the response is valid and we called the callback;
    121  *         #GNUNET_SYSERR if not (callback must still be called)
    122  */
    123 static enum GNUNET_GenericReturnValue
    124 process_ok_response (
    125   const struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh,
    126   const json_t *json)
    127 {
    128   struct TALER_EXCHANGE_PostRecoupWithdrawResponse rr = {
    129     .hr.reply = json,
    130     .hr.http_status = MHD_HTTP_OK
    131   };
    132   const json_t *j_recoups;
    133   struct TALER_RecoupedCoin *recoups;
    134   struct GNUNET_HashCode h_recoups;
    135   struct GNUNET_JSON_Specification spec[] = {
    136     GNUNET_JSON_spec_fixed_auto ("reserve_pub",
    137                                  &rr.details.ok.reserve_pub),
    138     GNUNET_JSON_spec_fixed_auto ("planchets_h",
    139                                  &rr.details.ok.planchets_h),
    140     GNUNET_JSON_spec_timestamp ("timestamp",
    141                                 &rr.details.ok.timestamp),
    142     TALER_JSON_spec_amount_any ("total_amount",
    143                                 &rr.details.ok.total_amount),
    144     GNUNET_JSON_spec_array_const ("recoups",
    145                                   &j_recoups),
    146     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    147                                  &rr.details.ok.exchange_sig),
    148     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    149                                  &rr.details.ok.exchange_pub),
    150     GNUNET_JSON_spec_end ()
    151   };
    152 
    153   if (GNUNET_OK !=
    154       GNUNET_JSON_parse (json,
    155                          spec,
    156                          NULL, NULL))
    157   {
    158     GNUNET_break_op (0);
    159     return GNUNET_SYSERR;
    160   }
    161   if ( (0 !=
    162         GNUNET_memcmp (&rr.details.ok.reserve_pub,
    163                        &prwh->reserve_pub)) ||
    164        (0 !=
    165         GNUNET_memcmp (&rr.details.ok.planchets_h,
    166                        &prwh->planchets_h)) )
    167   {
    168     GNUNET_break_op (0);
    169     return GNUNET_SYSERR;
    170   }
    171   if (GNUNET_OK !=
    172       TALER_EXCHANGE_test_signing_key (prwh->keys,
    173                                        &rr.details.ok.exchange_pub))
    174   {
    175     GNUNET_break_op (0);
    176     return GNUNET_SYSERR;
    177   }
    178   if (GNUNET_OK !=
    179       TALER_EXCHANGE_parse_recoups_ (j_recoups,
    180                                      prwh->num_recouped,
    181                                      prwh->recouped_pubs,
    182                                      &rr.details.ok.total_amount,
    183                                      &recoups,
    184                                      &h_recoups))
    185   {
    186     GNUNET_break_op (0);
    187     return GNUNET_SYSERR;
    188   }
    189   if (GNUNET_OK !=
    190       TALER_exchange_online_confirm_recoup_withdraw_batch_verify (
    191         rr.details.ok.timestamp,
    192         &rr.details.ok.reserve_pub,
    193         &rr.details.ok.planchets_h,
    194         &rr.details.ok.total_amount,
    195         &h_recoups,
    196         &rr.details.ok.exchange_pub,
    197         &rr.details.ok.exchange_sig))
    198   {
    199     GNUNET_break_op (0);
    200     GNUNET_free (recoups);
    201     return GNUNET_SYSERR;
    202   }
    203   rr.details.ok.num_recoups = prwh->num_recouped;
    204   rr.details.ok.recoups = recoups;
    205   prwh->cb (prwh->cb_cls,
    206             &rr);
    207   GNUNET_free (recoups);
    208   return GNUNET_OK;
    209 }
    210 
    211 
    212 /**
    213  * Find the recouped coin @a coin_pub and how we used it.
    214  *
    215  * @param cls a `struct TALER_EXCHANGE_PostRecoupWithdrawHandle *`
    216  * @param coin_pub public key of the coin named in the conflict reply
    217  * @param[out] h_denom_pub set to the hash of the denomination we used
    218  * @param[out] h_age_commitment set to the age commitment hash we used, or NULL
    219  * @return #GNUNET_OK if found, #GNUNET_NO if the coin is not ours
    220  */
    221 static enum GNUNET_GenericReturnValue
    222 recoup_coin_lookup (void *cls,
    223                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
    224                     const struct TALER_DenominationHashP **h_denom_pub,
    225                     const struct TALER_AgeCommitmentHashP **h_age_commitment)
    226 {
    227   struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh = cls;
    228 
    229   for (size_t i = 0; i < prwh->num_recouped; i++)
    230   {
    231     if (0 != GNUNET_memcmp (coin_pub,
    232                             &prwh->recouped_pubs[i]))
    233       continue;
    234     *h_denom_pub = &prwh->recouped_infos[i].h_denom_pub;
    235     *h_age_commitment = prwh->recouped_infos[i].have_age
    236       ? &prwh->recouped_infos[i].h_age_commitment
    237       : NULL;
    238     return GNUNET_OK;
    239   }
    240   return GNUNET_NO;
    241 }
    242 
    243 
    244 /**
    245  * Function called when we're done processing the
    246  * HTTP /recoup-withdraw request.
    247  *
    248  * @param cls the `struct TALER_EXCHANGE_PostRecoupWithdrawHandle`
    249  * @param response_code HTTP response code, 0 on error
    250  * @param response parsed JSON result, NULL on error
    251  */
    252 static void
    253 handle_recoup_withdraw_finished (void *cls,
    254                                  long response_code,
    255                                  const void *response)
    256 {
    257   struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh = cls;
    258   const json_t *j = response;
    259   struct TALER_EXCHANGE_PostRecoupWithdrawResponse rr = {
    260     .hr.reply = j,
    261     .hr.http_status = (unsigned int) response_code
    262   };
    263 
    264   prwh->job = NULL;
    265   switch (response_code)
    266   {
    267   case 0:
    268     rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    269     break;
    270   case MHD_HTTP_OK:
    271     if (GNUNET_OK !=
    272         process_ok_response (prwh,
    273                              j))
    274     {
    275       GNUNET_break_op (0);
    276       rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    277       rr.hr.http_status = 0;
    278       break;
    279     }
    280     TALER_EXCHANGE_post_recoup_withdraw_cancel (prwh);
    281     return;
    282   case MHD_HTTP_BAD_REQUEST:
    283     /* This should never happen, either us or the exchange is buggy
    284        (or API version conflict); just pass JSON reply to the application */
    285     rr.hr.ec = TALER_JSON_get_error_code (j);
    286     rr.hr.hint = TALER_JSON_get_error_hint (j);
    287     break;
    288   case MHD_HTTP_FORBIDDEN:
    289     /* Nothing really to verify, exchange says one of the signatures is
    290        invalid; as we checked them, this should never happen, we
    291        should pass the JSON reply to the application */
    292     rr.hr.ec = TALER_JSON_get_error_code (j);
    293     rr.hr.hint = TALER_JSON_get_error_hint (j);
    294     break;
    295   case MHD_HTTP_NOT_FOUND:
    296     /* Exchange does not know the denomination or the withdraw
    297        operation; pass the JSON reply to the application */
    298     rr.hr.ec = TALER_JSON_get_error_code (j);
    299     rr.hr.hint = TALER_JSON_get_error_hint (j);
    300     break;
    301   case MHD_HTTP_CONFLICT:
    302     rr.hr.ec = TALER_JSON_get_error_code (j);
    303     rr.hr.hint = TALER_JSON_get_error_hint (j);
    304     switch (rr.hr.ec)
    305     {
    306     case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
    307     case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
    308       if (GNUNET_OK !=
    309           TALER_EXCHANGE_check_coin_conflict_ (prwh->keys,
    310                                                rr.hr.ec,
    311                                                j,
    312                                                &recoup_coin_lookup,
    313                                                prwh,
    314                                                &rr.details.conflict))
    315       {
    316         GNUNET_break_op (0);
    317         rr.hr.http_status = 0;
    318         rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    319       }
    320       break;
    321     default:
    322       /* Commitment mismatch, denomination mismatch or a coin without
    323          residual value; pass the JSON reply to the application */
    324       break;
    325     }
    326     break;
    327   case MHD_HTTP_GONE:
    328     /* Denomination not eligible for recoup (not revoked, or expired) */
    329     rr.hr.ec = TALER_JSON_get_error_code (j);
    330     rr.hr.hint = TALER_JSON_get_error_hint (j);
    331     break;
    332   case MHD_HTTP_PRECONDITION_FAILED:
    333     /* Denomination not yet valid */
    334     rr.hr.ec = TALER_JSON_get_error_code (j);
    335     rr.hr.hint = TALER_JSON_get_error_hint (j);
    336     break;
    337   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    338     /* Server had an internal issue; we should retry, but this API
    339        leaves this to the application */
    340     rr.hr.ec = TALER_JSON_get_error_code (j);
    341     rr.hr.hint = TALER_JSON_get_error_hint (j);
    342     break;
    343   case MHD_HTTP_BAD_GATEWAY:
    344   case MHD_HTTP_SERVICE_UNAVAILABLE:
    345     /* Exchange is (temporarily) unable to sign; retry later */
    346     rr.hr.ec = TALER_JSON_get_error_code (j);
    347     rr.hr.hint = TALER_JSON_get_error_hint (j);
    348     break;
    349   default:
    350     /* unexpected response code */
    351     rr.hr.ec = TALER_JSON_get_error_code (j);
    352     rr.hr.hint = TALER_JSON_get_error_hint (j);
    353     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    354                 "Unexpected response code %u/%d for exchange recoup-withdraw\n",
    355                 (unsigned int) response_code,
    356                 (int) rr.hr.ec);
    357     GNUNET_break (0);
    358     break;
    359   }
    360   prwh->cb (prwh->cb_cls,
    361             &rr);
    362   TALER_EXCHANGE_free_coin_conflict_ (&rr.hr,
    363                                       &rr.details.conflict);
    364   TALER_EXCHANGE_post_recoup_withdraw_cancel (prwh);
    365 }
    366 
    367 
    368 struct TALER_EXCHANGE_PostRecoupWithdrawHandle *
    369 TALER_EXCHANGE_post_recoup_withdraw_create (
    370   struct GNUNET_CURL_Context *ctx,
    371   const char *url,
    372   struct TALER_EXCHANGE_Keys *keys,
    373   const struct TALER_ReservePublicKeyP *reserve_pub,
    374   const struct TALER_HashBlindedPlanchetsP *planchets_h,
    375   const struct TALER_BlindingMasterSeedP *blinding_seed,
    376   size_t num_coins,
    377   const struct TALER_EXCHANGE_RecoupCoin coins[static num_coins])
    378 {
    379   struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh;
    380   json_t *coin_data;
    381 
    382   prwh = GNUNET_new (struct TALER_EXCHANGE_PostRecoupWithdrawHandle);
    383   prwh->recouped_pubs = GNUNET_new_array (num_coins,
    384                                           struct TALER_CoinSpendPublicKeyP);
    385   prwh->recouped_infos = GNUNET_new_array (
    386     num_coins,
    387     struct TALER_EXCHANGE_RecoupedCoinInfo_);
    388   coin_data = TALER_EXCHANGE_recoup_coin_data_ (num_coins,
    389                                                 coins,
    390                                                 blinding_seed,
    391                                                 false, /* not for melt */
    392                                                 prwh->recouped_pubs,
    393                                                 prwh->recouped_infos,
    394                                                 &prwh->num_recouped);
    395   if (NULL == coin_data)
    396   {
    397     GNUNET_break (0);
    398     GNUNET_free (prwh->recouped_pubs);
    399     GNUNET_free (prwh->recouped_infos);
    400     GNUNET_free (prwh);
    401     return NULL;
    402   }
    403   prwh->ctx = ctx;
    404   prwh->base_url = GNUNET_strdup (url);
    405   prwh->keys = TALER_EXCHANGE_keys_incref (keys);
    406   prwh->reserve_pub = *reserve_pub;
    407   prwh->planchets_h = *planchets_h;
    408   prwh->body = GNUNET_JSON_PACK (
    409     GNUNET_JSON_pack_data_auto ("reserve_pub",
    410                                 reserve_pub),
    411     GNUNET_JSON_pack_data_auto ("planchets_h",
    412                                 planchets_h),
    413     GNUNET_JSON_pack_array_steal ("coin_data",
    414                                   coin_data));
    415   return prwh;
    416 }
    417 
    418 
    419 enum TALER_ErrorCode
    420 TALER_EXCHANGE_post_recoup_withdraw_start (
    421   struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh,
    422   TALER_EXCHANGE_PostRecoupWithdrawCallback cb,
    423   TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE *cb_cls)
    424 {
    425   CURL *eh;
    426 
    427   prwh->cb = cb;
    428   prwh->cb_cls = cb_cls;
    429   prwh->url = TALER_url_join (prwh->base_url,
    430                               "recoup-withdraw",
    431                               NULL);
    432   if (NULL == prwh->url)
    433   {
    434     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    435                 "Could not construct request URL.\n");
    436     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    437   }
    438   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    439               "URL for recoup-withdraw: `%s'\n",
    440               prwh->url);
    441   eh = TALER_EXCHANGE_curl_easy_get_ (prwh->url);
    442   if ( (NULL == eh) ||
    443        (GNUNET_OK !=
    444         TALER_curl_easy_post (&prwh->post_ctx,
    445                               eh,
    446                               prwh->body)) )
    447   {
    448     GNUNET_break (0);
    449     if (NULL != eh)
    450       curl_easy_cleanup (eh);
    451     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    452   }
    453   prwh->job = GNUNET_CURL_job_add2 (prwh->ctx,
    454                                     eh,
    455                                     prwh->post_ctx.headers,
    456                                     &handle_recoup_withdraw_finished,
    457                                     prwh);
    458   if (NULL == prwh->job)
    459     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    460   return TALER_EC_NONE;
    461 }
    462 
    463 
    464 void
    465 TALER_EXCHANGE_post_recoup_withdraw_cancel (
    466   struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh)
    467 {
    468   if (NULL != prwh->job)
    469   {
    470     GNUNET_CURL_job_cancel (prwh->job);
    471     prwh->job = NULL;
    472   }
    473   TALER_curl_easy_post_finished (&prwh->post_ctx);
    474   GNUNET_free (prwh->url);
    475   GNUNET_free (prwh->base_url);
    476   GNUNET_free (prwh->recouped_pubs);
    477   GNUNET_free (prwh->recouped_infos);
    478   json_decref (prwh->body);
    479   TALER_EXCHANGE_keys_decref (prwh->keys);
    480   GNUNET_free (prwh);
    481 }
    482 
    483 
    484 /* end of exchange_api_post-recoup-withdraw.c */