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_post-reserves-RESERVE_PUB-open.c (15597B)


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