exchange

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

taler-exchange-httpd_reserves_open.c (14518B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022 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 taler-exchange-httpd_reserves_open.c
     18  * @brief Handle /reserves/$RESERVE_PUB/open requests
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include <gnunet/gnunet_util_lib.h>
     23 #include <jansson.h>
     24 #include "taler/taler_mhd_lib.h"
     25 #include "taler/taler_json_lib.h"
     26 #include "taler/taler_dbevents.h"
     27 #include "taler-exchange-httpd_common_deposit.h"
     28 #include "taler-exchange-httpd_keys.h"
     29 #include "taler-exchange-httpd_reserves_open.h"
     30 #include "taler-exchange-httpd_responses.h"
     31 
     32 
     33 /**
     34  * How far do we allow a client's time to be off when
     35  * checking the request timestamp?
     36  */
     37 #define TIMESTAMP_TOLERANCE \
     38         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
     39 
     40 
     41 /**
     42  * Closure for #reserve_open_transaction.
     43  */
     44 struct ReserveOpenContext
     45 {
     46   /**
     47    * Public key of the reserve the inquiry is about.
     48    */
     49   const struct TALER_ReservePublicKeyP *reserve_pub;
     50 
     51   /**
     52    * Desired (minimum) expiration time for the reserve.
     53    */
     54   struct GNUNET_TIME_Timestamp desired_expiration;
     55 
     56   /**
     57    * Actual expiration time for the reserve.
     58    */
     59   struct GNUNET_TIME_Timestamp reserve_expiration;
     60 
     61   /**
     62    * Timestamp of the request.
     63    */
     64   struct GNUNET_TIME_Timestamp timestamp;
     65 
     66   /**
     67    * Client signature approving the request.
     68    */
     69   struct TALER_ReserveSignatureP reserve_sig;
     70 
     71   /**
     72    * Global fees applying to the request.
     73    */
     74   const struct TEH_GlobalFee *gf;
     75 
     76   /**
     77    * Amount to be paid from the reserve.
     78    */
     79   struct TALER_Amount reserve_payment;
     80 
     81   /**
     82    * Actual cost to open the reserve.
     83    */
     84   struct TALER_Amount open_cost;
     85 
     86   /**
     87    * Total amount that was deposited.
     88    */
     89   struct TALER_Amount total;
     90 
     91   /**
     92    * Information about payments by coin.
     93    */
     94   struct TEH_PurseDepositedCoin *payments;
     95 
     96   /**
     97    * Length of the @e payments array.
     98    */
     99   unsigned int payments_len;
    100 
    101   /**
    102    * Desired minimum purse limit.
    103    */
    104   uint32_t purse_limit;
    105 
    106   /**
    107    * Set to true if the reserve balance is too low
    108    * for the operation.
    109    */
    110   bool no_funds;
    111 
    112 };
    113 
    114 
    115 /**
    116  * Send reserve open to client.
    117  *
    118  * @param connection connection to the client
    119  * @param rsc reserve open data to return
    120  * @return MHD result code
    121  */
    122 static MHD_RESULT
    123 reply_reserve_open_success (struct MHD_Connection *connection,
    124                             const struct ReserveOpenContext *rsc)
    125 {
    126   struct GNUNET_TIME_Timestamp now;
    127   struct GNUNET_TIME_Timestamp re;
    128   unsigned int status;
    129 
    130   status = MHD_HTTP_OK;
    131   if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
    132                                  <,
    133                                  rsc->desired_expiration))
    134     status = MHD_HTTP_PAYMENT_REQUIRED;
    135   now = GNUNET_TIME_timestamp_get ();
    136   if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
    137                                  <,
    138                                  now))
    139     re = now;
    140   else
    141     re = rsc->reserve_expiration;
    142   return TALER_MHD_REPLY_JSON_PACK (
    143     connection,
    144     status,
    145     GNUNET_JSON_pack_timestamp ("reserve_expiration",
    146                                 re),
    147     TALER_JSON_pack_amount ("open_cost",
    148                             &rsc->open_cost));
    149 }
    150 
    151 
    152 /**
    153  * Cleans up information in @a rsc, but does not
    154  * free @a rsc itself (allocated on the stack!).
    155  *
    156  * @param[in] rsc struct with information to clean up
    157  */
    158 static void
    159 cleanup_rsc (struct ReserveOpenContext *rsc)
    160 {
    161   for (unsigned int i = 0; i<rsc->payments_len; i++)
    162   {
    163     TEH_common_purse_deposit_free_coin (&rsc->payments[i]);
    164   }
    165   GNUNET_free (rsc->payments);
    166 }
    167 
    168 
    169 /**
    170  * Function implementing /reserves/$RID/open transaction.  Given the public
    171  * key of a reserve, return the associated transaction open.  Runs the
    172  * transaction logic; IF it returns a non-error code, the transaction logic
    173  * MUST NOT queue a MHD response.  IF it returns an hard error, the
    174  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF it
    175  * returns the soft error code, the function MAY be called again to retry and
    176  * MUST not queue a MHD response.
    177  *
    178  * @param cls a `struct ReserveOpenContext *`
    179  * @param connection MHD request which triggered the transaction
    180  * @param[out] mhd_ret set to MHD response status for @a connection,
    181  *             if transaction failed (!)
    182  * @return transaction status
    183  */
    184 static enum GNUNET_DB_QueryStatus
    185 reserve_open_transaction (void *cls,
    186                           struct MHD_Connection *connection,
    187                           MHD_RESULT *mhd_ret)
    188 {
    189   struct ReserveOpenContext *rsc = cls;
    190   enum GNUNET_DB_QueryStatus qs;
    191   struct TALER_Amount reserve_balance;
    192 
    193   for (unsigned int i = 0; i<rsc->payments_len; i++)
    194   {
    195     struct TEH_PurseDepositedCoin *coin = &rsc->payments[i];
    196     bool insufficient_funds = true;
    197 
    198     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    199                 "Make coin %u known\n",
    200                 i);
    201     qs = TEH_make_coin_known (&coin->cpi,
    202                               connection,
    203                               &coin->known_coin_id,
    204                               mhd_ret);
    205     if (qs < 0)
    206       return qs;
    207     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    208                 "Insert open deposit %u known\n",
    209                 i);
    210     qs = TEH_plugin->insert_reserve_open_deposit (
    211       TEH_plugin->cls,
    212       &coin->cpi,
    213       &coin->coin_sig,
    214       coin->known_coin_id,
    215       &coin->amount,
    216       &rsc->reserve_sig,
    217       rsc->reserve_pub,
    218       &insufficient_funds);
    219     /* 0 == qs is fine, then the coin was already
    220        spent for this very operation as identified
    221        by reserve_sig! */
    222     if (qs < 0)
    223     {
    224       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    225         return qs;
    226       GNUNET_break (0);
    227       *mhd_ret = TALER_MHD_reply_with_error (connection,
    228                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    229                                              TALER_EC_GENERIC_DB_STORE_FAILED,
    230                                              "insert_reserve_open_deposit");
    231       return qs;
    232     }
    233     if (insufficient_funds)
    234     {
    235       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    236                   "Handle insufficient funds\n");
    237       *mhd_ret
    238         = TEH_RESPONSE_reply_coin_insufficient_funds (
    239             connection,
    240             TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
    241             &coin->cpi.denom_pub_hash,
    242             &coin->cpi.coin_pub);
    243       return GNUNET_DB_STATUS_HARD_ERROR;
    244     }
    245   }
    246 
    247   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    248               "Do reserve open with reserve payment of %s\n",
    249               TALER_amount2s (&rsc->total));
    250   qs = TEH_plugin->do_reserve_open (TEH_plugin->cls,
    251                                     /* inputs */
    252                                     rsc->reserve_pub,
    253                                     &rsc->total,
    254                                     &rsc->reserve_payment,
    255                                     rsc->purse_limit,
    256                                     &rsc->reserve_sig,
    257                                     rsc->desired_expiration,
    258                                     rsc->timestamp,
    259                                     &rsc->gf->fees.account,
    260                                     /* outputs */
    261                                     &rsc->no_funds,
    262                                     &reserve_balance,
    263                                     &rsc->open_cost,
    264                                     &rsc->reserve_expiration);
    265   switch (qs)
    266   {
    267   case GNUNET_DB_STATUS_HARD_ERROR:
    268     GNUNET_break (0);
    269     *mhd_ret
    270       = TALER_MHD_reply_with_error (connection,
    271                                     MHD_HTTP_INTERNAL_SERVER_ERROR,
    272                                     TALER_EC_GENERIC_DB_FETCH_FAILED,
    273                                     "do_reserve_open");
    274     return GNUNET_DB_STATUS_HARD_ERROR;
    275   case GNUNET_DB_STATUS_SOFT_ERROR:
    276     return qs;
    277   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    278     *mhd_ret
    279       = TALER_MHD_reply_with_error (connection,
    280                                     MHD_HTTP_NOT_FOUND,
    281                                     TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
    282                                     NULL);
    283     return GNUNET_DB_STATUS_HARD_ERROR;
    284   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    285     break;
    286   }
    287   if (rsc->no_funds)
    288   {
    289     TEH_plugin->rollback (TEH_plugin->cls);
    290     *mhd_ret
    291       = TEH_RESPONSE_reply_reserve_insufficient_balance (
    292           connection,
    293           TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS,
    294           &reserve_balance,
    295           &rsc->reserve_payment,
    296           rsc->reserve_pub);
    297     return GNUNET_DB_STATUS_HARD_ERROR;
    298   }
    299   return qs;
    300 }
    301 
    302 
    303 MHD_RESULT
    304 TEH_handler_reserves_open (struct TEH_RequestContext *rc,
    305                            const struct TALER_ReservePublicKeyP *reserve_pub,
    306                            const json_t *root)
    307 {
    308   struct ReserveOpenContext rsc;
    309   const json_t *payments;
    310   struct GNUNET_JSON_Specification spec[] = {
    311     GNUNET_JSON_spec_timestamp ("request_timestamp",
    312                                 &rsc.timestamp),
    313     GNUNET_JSON_spec_timestamp ("reserve_expiration",
    314                                 &rsc.desired_expiration),
    315     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    316                                  &rsc.reserve_sig),
    317     GNUNET_JSON_spec_uint32 ("purse_limit",
    318                              &rsc.purse_limit),
    319     GNUNET_JSON_spec_array_const ("payments",
    320                                   &payments),
    321     TALER_JSON_spec_amount ("reserve_payment",
    322                             TEH_currency,
    323                             &rsc.reserve_payment),
    324     GNUNET_JSON_spec_end ()
    325   };
    326 
    327   rsc.reserve_pub = reserve_pub;
    328   {
    329     enum GNUNET_GenericReturnValue res;
    330 
    331     res = TALER_MHD_parse_json_data (rc->connection,
    332                                      root,
    333                                      spec);
    334     if (GNUNET_SYSERR == res)
    335     {
    336       GNUNET_break (0);
    337       return MHD_NO; /* hard failure */
    338     }
    339     if (GNUNET_NO == res)
    340     {
    341       GNUNET_break_op (0);
    342       return MHD_YES; /* failure */
    343     }
    344   }
    345 
    346   {
    347     struct GNUNET_TIME_Timestamp now;
    348 
    349     now = GNUNET_TIME_timestamp_get ();
    350     if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
    351                                           rsc.timestamp.abs_time,
    352                                           TIMESTAMP_TOLERANCE))
    353     {
    354       GNUNET_break_op (0);
    355       return TALER_MHD_reply_with_error (rc->connection,
    356                                          MHD_HTTP_BAD_REQUEST,
    357                                          TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
    358                                          NULL);
    359     }
    360   }
    361 
    362   rsc.payments_len = json_array_size (payments);
    363   rsc.payments = GNUNET_new_array (rsc.payments_len,
    364                                    struct TEH_PurseDepositedCoin);
    365   rsc.total = rsc.reserve_payment;
    366   for (unsigned int i = 0; i<rsc.payments_len; i++)
    367   {
    368     struct TEH_PurseDepositedCoin *coin = &rsc.payments[i];
    369     enum GNUNET_GenericReturnValue res;
    370 
    371     res = TEH_common_purse_deposit_parse_coin (
    372       rc->connection,
    373       coin,
    374       json_array_get (payments,
    375                       i));
    376     if (GNUNET_SYSERR == res)
    377     {
    378       GNUNET_break (0);
    379       cleanup_rsc (&rsc);
    380       return MHD_NO;   /* hard failure */
    381     }
    382     if (GNUNET_NO == res)
    383     {
    384       GNUNET_break_op (0);
    385       cleanup_rsc (&rsc);
    386       return MHD_YES;   /* failure */
    387     }
    388     if (0 >
    389         TALER_amount_add (&rsc.total,
    390                           &rsc.total,
    391                           &coin->amount_minus_fee))
    392     {
    393       GNUNET_break (0);
    394       cleanup_rsc (&rsc);
    395       return TALER_MHD_reply_with_error (rc->connection,
    396                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    397                                          TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
    398                                          NULL);
    399     }
    400   }
    401 
    402   {
    403     struct TEH_KeyStateHandle *keys;
    404 
    405     keys = TEH_keys_get_state ();
    406     if (NULL == keys)
    407     {
    408       GNUNET_break (0);
    409       cleanup_rsc (&rsc);
    410       return TALER_MHD_reply_with_error (rc->connection,
    411                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    412                                          TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
    413                                          NULL);
    414     }
    415     rsc.gf = TEH_keys_global_fee_by_time (keys,
    416                                           rsc.timestamp);
    417   }
    418   if (NULL == rsc.gf)
    419   {
    420     GNUNET_break (0);
    421     cleanup_rsc (&rsc);
    422     return TALER_MHD_reply_with_error (rc->connection,
    423                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    424                                        TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
    425                                        NULL);
    426   }
    427 
    428   if (GNUNET_OK !=
    429       TALER_wallet_reserve_open_verify (&rsc.reserve_payment,
    430                                         rsc.timestamp,
    431                                         rsc.desired_expiration,
    432                                         rsc.purse_limit,
    433                                         reserve_pub,
    434                                         &rsc.reserve_sig))
    435   {
    436     GNUNET_break_op (0);
    437     cleanup_rsc (&rsc);
    438     return TALER_MHD_reply_with_error (rc->connection,
    439                                        MHD_HTTP_FORBIDDEN,
    440                                        TALER_EC_EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE,
    441                                        NULL);
    442   }
    443 
    444   {
    445     MHD_RESULT mhd_ret;
    446 
    447     if (GNUNET_OK !=
    448         TEH_DB_run_transaction (rc->connection,
    449                                 "reserve open",
    450                                 TEH_MT_REQUEST_OTHER,
    451                                 &mhd_ret,
    452                                 &reserve_open_transaction,
    453                                 &rsc))
    454     {
    455       cleanup_rsc (&rsc);
    456       return mhd_ret;
    457     }
    458   }
    459 
    460   {
    461     MHD_RESULT mhd_ret;
    462 
    463     mhd_ret = reply_reserve_open_success (rc->connection,
    464                                           &rsc);
    465     cleanup_rsc (&rsc);
    466     return mhd_ret;
    467   }
    468 }
    469 
    470 
    471 /* end of taler-exchange-httpd_reserves_open.c */