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_purse.c (26453B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-2024 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_purse.c
     18  * @brief Handle /reserves/$RID/purse requests; parses the POST and JSON and
     19  *        verifies the coin signature before handing things off
     20  *        to the database.
     21  * @author Christian Grothoff
     22  */
     23 #include "taler/platform.h"
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <jansson.h>
     27 #include <microhttpd.h>
     28 #include <pthread.h>
     29 #include "taler/taler_json_lib.h"
     30 #include "taler/taler_kyclogic_lib.h"
     31 #include "taler/taler_mhd_lib.h"
     32 #include "taler-exchange-httpd_common_kyc.h"
     33 #include "taler-exchange-httpd_reserves_purse.h"
     34 #include "taler-exchange-httpd_responses.h"
     35 #include "taler/taler_exchangedb_lib.h"
     36 #include "taler-exchange-httpd_keys.h"
     37 
     38 
     39 /**
     40  * Closure for #purse_transaction.
     41  */
     42 struct ReservePurseContext
     43 {
     44 
     45   /**
     46    * Kept in a DLL.
     47    */
     48   struct ReservePurseContext *next;
     49 
     50   /**
     51    * Kept in a DLL.
     52    */
     53   struct ReservePurseContext *prev;
     54 
     55   /**
     56    * Our request context.
     57    */
     58   struct TEH_RequestContext *rc;
     59 
     60   /**
     61    * Handle for legitimization check.
     62    */
     63   struct TEH_LegitimizationCheckHandle *lch;
     64 
     65   /**
     66    * Response to return. Note that the response must
     67    * be queued or destroyed by the callee.  NULL
     68    * if the legitimization check was successful and the handler should return
     69    * a handler-specific result.
     70    */
     71   struct MHD_Response *response;
     72 
     73   /**
     74    * Payto URI for the reserve.
     75    */
     76   struct TALER_NormalizedPayto payto_uri;
     77 
     78   /**
     79    * Public key of the account (reserve) we are creating a purse for.
     80    */
     81   union TALER_AccountPublicKeyP account_pub;
     82 
     83   /**
     84    * Signature of the reserve affirming the merge.
     85    */
     86   struct TALER_ReserveSignatureP reserve_sig;
     87 
     88   /**
     89    * Purse fee the client is willing to pay.
     90    */
     91   struct TALER_Amount purse_fee;
     92 
     93   /**
     94    * Total amount already put into the purse.
     95    */
     96   struct TALER_Amount deposit_total;
     97 
     98   /**
     99    * Merge time.
    100    */
    101   struct GNUNET_TIME_Timestamp merge_timestamp;
    102 
    103   /**
    104    * Our current time.
    105    */
    106   struct GNUNET_TIME_Timestamp exchange_timestamp;
    107 
    108   /**
    109    * Details about an encrypted contract, if any.
    110    */
    111   struct TALER_EncryptedContract econtract;
    112 
    113   /**
    114    * Merge key for the purse.
    115    */
    116   struct TALER_PurseMergePublicKeyP merge_pub;
    117 
    118   /**
    119    * Merge affirmation by the @e merge_pub.
    120    */
    121   struct TALER_PurseMergeSignatureP merge_sig;
    122 
    123   /**
    124    * Signature of the client affiming this request.
    125    */
    126   struct TALER_PurseContractSignatureP purse_sig;
    127 
    128   /**
    129    * Fundamental details about the purse.
    130    */
    131   struct TEH_PurseDetails pd;
    132 
    133   /**
    134    * Hash of the @e payto_uri.
    135    */
    136   struct TALER_NormalizedPaytoHashP h_payto;
    137 
    138   /**
    139    * KYC status of the operation.
    140    */
    141   struct TALER_EXCHANGEDB_KycStatus kyc;
    142 
    143   /**
    144    * Minimum age for deposits into this purse.
    145    */
    146   uint32_t min_age;
    147 
    148   /**
    149    * Flags for the operation.
    150    */
    151   enum TALER_WalletAccountMergeFlags flags;
    152 
    153   /**
    154    * HTTP status code for @a response, or 0
    155    */
    156   unsigned int http_status;
    157 
    158   /**
    159    * Do we lack an @e econtract?
    160    */
    161   bool no_econtract;
    162 
    163   /**
    164    * Set to true if the purse_fee was not given in the REST request.
    165    */
    166   bool no_purse_fee;
    167 
    168 };
    169 
    170 /**
    171  * Kept in a DLL.
    172  */
    173 static struct ReservePurseContext *rpc_head;
    174 
    175 /**
    176  * Kept in a DLL.
    177  */
    178 static struct ReservePurseContext *rpc_tail;
    179 
    180 
    181 void
    182 TEH_reserves_purse_cleanup ()
    183 {
    184   struct ReservePurseContext *rpc;
    185 
    186   while (NULL != (rpc = rpc_head))
    187   {
    188     GNUNET_CONTAINER_DLL_remove (rpc_head,
    189                                  rpc_tail,
    190                                  rpc);
    191     MHD_resume_connection (rpc->rc->connection);
    192   }
    193 }
    194 
    195 
    196 /**
    197  * Function called to iterate over KYC-relevant
    198  * transaction amounts for a particular time range.
    199  * Called within a database transaction, so must
    200  * not start a new one.
    201  *
    202  * @param cls a `struct ReservePurseContext`
    203  * @param limit maximum time-range for which events
    204  *        should be fetched (timestamp in the past)
    205  * @param cb function to call on each event found,
    206  *        events must be returned in reverse chronological
    207  *        order
    208  * @param cb_cls closure for @a cb
    209  * @return transaction status
    210  */
    211 static enum GNUNET_DB_QueryStatus
    212 amount_iterator (void *cls,
    213                  struct GNUNET_TIME_Absolute limit,
    214                  TALER_EXCHANGEDB_KycAmountCallback cb,
    215                  void *cb_cls)
    216 {
    217   struct ReservePurseContext *rpc = cls;
    218   enum GNUNET_DB_QueryStatus qs;
    219   enum GNUNET_GenericReturnValue ret;
    220 
    221   ret = cb (cb_cls,
    222             &rpc->pd.target_amount,
    223             GNUNET_TIME_absolute_get ());
    224   GNUNET_break (GNUNET_SYSERR != ret);
    225   if (GNUNET_OK != ret)
    226     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    227   qs = TEH_plugin->select_merge_amounts_for_kyc_check (
    228     TEH_plugin->cls,
    229     &rpc->h_payto,
    230     limit,
    231     cb,
    232     cb_cls);
    233   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    234               "Got %d additional transactions for this merge and limit %llu\n",
    235               qs,
    236               (unsigned long long) limit.abs_value_us);
    237   GNUNET_break (qs >= 0);
    238   return qs;
    239 }
    240 
    241 
    242 /**
    243  * Function called with the result of a legitimization
    244  * check.
    245  *
    246  * @param cls closure
    247  * @param lcr legitimization check result
    248  */
    249 static void
    250 reserve_purse_legi_cb (
    251   void *cls,
    252   const struct TEH_LegitimizationCheckResult *lcr)
    253 {
    254   struct ReservePurseContext *rpc = cls;
    255 
    256   rpc->lch = NULL;
    257   rpc->http_status = lcr->http_status;
    258   rpc->response = lcr->response;
    259   rpc->kyc = lcr->kyc;
    260   GNUNET_CONTAINER_DLL_remove (rpc_head,
    261                                rpc_tail,
    262                                rpc);
    263   MHD_resume_connection (rpc->rc->connection);
    264   TALER_MHD_daemon_trigger ();
    265 }
    266 
    267 
    268 /**
    269  * Execute database transaction for /reserves/$PID/purse.  Runs the transaction
    270  * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
    271  * a MHD response.  IF it returns an hard error, the transaction logic MUST
    272  * queue a MHD response and set @a mhd_ret.  IF it returns the soft error
    273  * code, the function MAY be called again to retry and MUST not queue a MHD
    274  * response.
    275  *
    276  * @param cls a `struct ReservePurseContext`
    277  * @param connection MHD request context
    278  * @param[out] mhd_ret set to MHD status on error
    279  * @return transaction status
    280  */
    281 static enum GNUNET_DB_QueryStatus
    282 purse_transaction (void *cls,
    283                    struct MHD_Connection *connection,
    284                    MHD_RESULT *mhd_ret)
    285 {
    286   struct ReservePurseContext *rpc = cls;
    287   enum GNUNET_DB_QueryStatus qs;
    288 
    289   {
    290     bool in_conflict = true;
    291 
    292     /* 1) store purse */
    293     qs = TEH_plugin->insert_purse_request (
    294       TEH_plugin->cls,
    295       &rpc->pd.purse_pub,
    296       &rpc->merge_pub,
    297       rpc->pd.purse_expiration,
    298       &rpc->pd.h_contract_terms,
    299       rpc->min_age,
    300       rpc->flags,
    301       &rpc->purse_fee,
    302       &rpc->pd.target_amount,
    303       &rpc->purse_sig,
    304       &in_conflict);
    305     if (qs < 0)
    306     {
    307       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    308         return qs;
    309       GNUNET_break (0);
    310       *mhd_ret =
    311         TALER_MHD_reply_with_error (
    312           connection,
    313           MHD_HTTP_INTERNAL_SERVER_ERROR,
    314           TALER_EC_GENERIC_DB_STORE_FAILED,
    315           "insert purse request");
    316       return GNUNET_DB_STATUS_HARD_ERROR;
    317     }
    318     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    319       return qs;
    320     if (in_conflict)
    321     {
    322       struct TALER_PurseMergePublicKeyP merge_pub;
    323       struct GNUNET_TIME_Timestamp purse_expiration;
    324       struct TALER_PrivateContractHashP h_contract_terms;
    325       struct TALER_Amount target_amount;
    326       struct TALER_Amount balance;
    327       struct TALER_PurseContractSignatureP purse_sig;
    328       uint32_t min_age;
    329 
    330       TEH_plugin->rollback (TEH_plugin->cls);
    331       qs = TEH_plugin->get_purse_request (
    332         TEH_plugin->cls,
    333         &rpc->pd.purse_pub,
    334         &merge_pub,
    335         &purse_expiration,
    336         &h_contract_terms,
    337         &min_age,
    338         &target_amount,
    339         &balance,
    340         &purse_sig);
    341       if (qs <= 0)
    342       {
    343         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    344         GNUNET_break (0 != qs);
    345         TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
    346         *mhd_ret = TALER_MHD_reply_with_error (
    347           connection,
    348           MHD_HTTP_INTERNAL_SERVER_ERROR,
    349           TALER_EC_GENERIC_DB_FETCH_FAILED,
    350           "select purse request");
    351         return GNUNET_DB_STATUS_HARD_ERROR;
    352       }
    353       *mhd_ret
    354         = TALER_MHD_REPLY_JSON_PACK (
    355             connection,
    356             MHD_HTTP_CONFLICT,
    357             TALER_JSON_pack_ec (
    358               TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA),
    359             TALER_JSON_pack_amount ("amount",
    360                                     &target_amount),
    361             GNUNET_JSON_pack_uint64 ("min_age",
    362                                      min_age),
    363             GNUNET_JSON_pack_timestamp ("purse_expiration",
    364                                         purse_expiration),
    365             GNUNET_JSON_pack_data_auto ("purse_sig",
    366                                         &purse_sig),
    367             GNUNET_JSON_pack_data_auto ("h_contract_terms",
    368                                         &h_contract_terms),
    369             GNUNET_JSON_pack_data_auto ("merge_pub",
    370                                         &merge_pub));
    371       return GNUNET_DB_STATUS_HARD_ERROR;
    372     }
    373 
    374   }
    375 
    376   /* 2) create purse with reserve (and debit reserve for purse creation!) */
    377   {
    378     bool in_conflict = true;
    379     bool insufficient_funds = true;
    380     bool no_reserve = true;
    381 
    382     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    383                 "Creating purse with flags %d\n",
    384                 rpc->flags);
    385     qs = TEH_plugin->do_reserve_purse (
    386       TEH_plugin->cls,
    387       &rpc->pd.purse_pub,
    388       &rpc->merge_sig,
    389       rpc->merge_timestamp,
    390       &rpc->reserve_sig,
    391       (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
    392        == rpc->flags)
    393       ? NULL
    394       : &rpc->purse_fee,
    395       &rpc->account_pub.reserve_pub,
    396       &in_conflict,
    397       &no_reserve,
    398       &insufficient_funds);
    399     if (qs < 0)
    400     {
    401       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    402         return qs;
    403       TALER_LOG_WARNING (
    404         "Failed to store purse merge information in database\n");
    405       *mhd_ret =
    406         TALER_MHD_reply_with_error (
    407           connection,
    408           MHD_HTTP_INTERNAL_SERVER_ERROR,
    409           TALER_EC_GENERIC_DB_STORE_FAILED,
    410           "do reserve purse");
    411       return GNUNET_DB_STATUS_HARD_ERROR;
    412     }
    413     if (in_conflict)
    414     {
    415       /* same purse already merged into a different reserve!? */
    416       struct TALER_PurseContractPublicKeyP purse_pub;
    417       struct TALER_PurseMergeSignatureP merge_sig;
    418       struct GNUNET_TIME_Timestamp merge_timestamp;
    419       char *partner_url;
    420       struct TALER_ReservePublicKeyP reserve_pub;
    421       bool refunded;
    422 
    423       TEH_plugin->rollback (TEH_plugin->cls);
    424       qs = TEH_plugin->select_purse_merge (
    425         TEH_plugin->cls,
    426         &purse_pub,
    427         &merge_sig,
    428         &merge_timestamp,
    429         &partner_url,
    430         &reserve_pub,
    431         &refunded);
    432       if (qs <= 0)
    433       {
    434         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    435         GNUNET_break (0 != qs);
    436         TALER_LOG_WARNING (
    437           "Failed to fetch purse merge information from database\n");
    438         *mhd_ret = TALER_MHD_reply_with_error (
    439           connection,
    440           MHD_HTTP_INTERNAL_SERVER_ERROR,
    441           TALER_EC_GENERIC_DB_FETCH_FAILED,
    442           "select purse merge");
    443         return GNUNET_DB_STATUS_HARD_ERROR;
    444       }
    445       if (refunded)
    446       {
    447         /* This is a bit of a strange case ... */
    448         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    449                     "Purse was already refunded\n");
    450         *mhd_ret = TALER_MHD_reply_with_error (
    451           connection,
    452           MHD_HTTP_GONE,
    453           TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
    454           NULL);
    455         GNUNET_free (partner_url);
    456         return GNUNET_DB_STATUS_HARD_ERROR;
    457       }
    458       *mhd_ret
    459         = TALER_MHD_REPLY_JSON_PACK (
    460             connection,
    461             MHD_HTTP_CONFLICT,
    462             TALER_JSON_pack_ec (
    463               TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA),
    464             GNUNET_JSON_pack_string ("partner_url",
    465                                      NULL == partner_url
    466                                      ? TEH_base_url
    467                                      : partner_url),
    468             GNUNET_JSON_pack_timestamp ("merge_timestamp",
    469                                         merge_timestamp),
    470             GNUNET_JSON_pack_data_auto ("merge_sig",
    471                                         &merge_sig),
    472             GNUNET_JSON_pack_data_auto ("reserve_pub",
    473                                         &reserve_pub));
    474       GNUNET_free (partner_url);
    475       return GNUNET_DB_STATUS_HARD_ERROR;
    476     }
    477     if ( (no_reserve) &&
    478          ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
    479             == rpc->flags) ||
    480            (! TALER_amount_is_zero (&rpc->purse_fee)) ) )
    481     {
    482       *mhd_ret
    483         = TALER_MHD_REPLY_JSON_PACK (
    484             connection,
    485             MHD_HTTP_NOT_FOUND,
    486             TALER_JSON_pack_ec (
    487               TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN));
    488       return GNUNET_DB_STATUS_HARD_ERROR;
    489     }
    490     if (insufficient_funds)
    491     {
    492       *mhd_ret
    493         = TALER_MHD_REPLY_JSON_PACK (
    494             connection,
    495             MHD_HTTP_CONFLICT,
    496             TALER_JSON_pack_ec (
    497               TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS));
    498       return GNUNET_DB_STATUS_HARD_ERROR;
    499     }
    500   }
    501   /* 3) if present, persist contract */
    502   if (! rpc->no_econtract)
    503   {
    504     bool in_conflict = true;
    505 
    506     qs = TEH_plugin->insert_contract (TEH_plugin->cls,
    507                                       &rpc->pd.purse_pub,
    508                                       &rpc->econtract,
    509                                       &in_conflict);
    510     if (qs < 0)
    511     {
    512       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    513         return qs;
    514       TALER_LOG_WARNING ("Failed to store purse information in database\n");
    515       *mhd_ret = TALER_MHD_reply_with_error (
    516         connection,
    517         MHD_HTTP_INTERNAL_SERVER_ERROR,
    518         TALER_EC_GENERIC_DB_STORE_FAILED,
    519         "purse purse contract");
    520       return GNUNET_DB_STATUS_HARD_ERROR;
    521     }
    522     if (in_conflict)
    523     {
    524       struct TALER_EncryptedContract econtract;
    525       struct GNUNET_HashCode h_econtract;
    526 
    527       qs = TEH_plugin->select_contract_by_purse (
    528         TEH_plugin->cls,
    529         &rpc->pd.purse_pub,
    530         &econtract);
    531       if (qs <= 0)
    532       {
    533         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    534           return qs;
    535         GNUNET_break (0 != qs);
    536         TALER_LOG_WARNING (
    537           "Failed to store fetch contract information from database\n");
    538         *mhd_ret = TALER_MHD_reply_with_error (
    539           connection,
    540           MHD_HTTP_INTERNAL_SERVER_ERROR,
    541           TALER_EC_GENERIC_DB_FETCH_FAILED,
    542           "select contract");
    543         return GNUNET_DB_STATUS_HARD_ERROR;
    544       }
    545       GNUNET_CRYPTO_hash (econtract.econtract,
    546                           econtract.econtract_size,
    547                           &h_econtract);
    548       *mhd_ret
    549         = TALER_MHD_REPLY_JSON_PACK (
    550             connection,
    551             MHD_HTTP_CONFLICT,
    552             TALER_JSON_pack_ec (
    553               TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
    554             GNUNET_JSON_pack_data_auto ("h_econtract",
    555                                         &h_econtract),
    556             GNUNET_JSON_pack_data_auto ("econtract_sig",
    557                                         &econtract.econtract_sig),
    558             GNUNET_JSON_pack_data_auto ("contract_pub",
    559                                         &econtract.contract_pub));
    560       return GNUNET_DB_STATUS_HARD_ERROR;
    561     }
    562   }
    563   return qs;
    564 }
    565 
    566 
    567 /**
    568  * Function to clean up our rh_ctx in @a rc
    569  *
    570  * @param[in,out] rc context to clean up
    571  */
    572 static void
    573 rpc_cleaner (struct TEH_RequestContext *rc)
    574 {
    575   struct ReservePurseContext *rpc = rc->rh_ctx;
    576 
    577   if (NULL != rpc->lch)
    578   {
    579     TEH_legitimization_check_cancel (rpc->lch);
    580     rpc->lch = NULL;
    581   }
    582   GNUNET_free (rpc->econtract.econtract);
    583   GNUNET_free (rpc->payto_uri.normalized_payto);
    584   GNUNET_free (rpc);
    585 }
    586 
    587 
    588 MHD_RESULT
    589 TEH_handler_reserves_purse (
    590   struct TEH_RequestContext *rc,
    591   const struct TALER_ReservePublicKeyP *reserve_pub,
    592   const json_t *root)
    593 {
    594   struct ReservePurseContext *rpc = rc->rh_ctx;
    595   struct MHD_Connection *connection = rc->connection;
    596 
    597   if (NULL == rpc)
    598   {
    599     rpc = GNUNET_new (struct ReservePurseContext);
    600     rc->rh_ctx = rpc;
    601     rc->rh_cleaner = &rpc_cleaner;
    602     rpc->rc = rc;
    603     rpc->account_pub.reserve_pub = *reserve_pub;
    604     rpc->exchange_timestamp
    605       = GNUNET_TIME_timestamp_get ();
    606     rpc->no_purse_fee = true;
    607 
    608 
    609     {
    610       struct GNUNET_JSON_Specification spec[] = {
    611         TALER_JSON_spec_amount ("purse_value",
    612                                 TEH_currency,
    613                                 &rpc->pd.target_amount),
    614         GNUNET_JSON_spec_uint32 ("min_age",
    615                                  &rpc->min_age),
    616         GNUNET_JSON_spec_mark_optional (
    617           TALER_JSON_spec_amount ("purse_fee",
    618                                   TEH_currency,
    619                                   &rpc->purse_fee),
    620           &rpc->no_purse_fee),
    621         GNUNET_JSON_spec_mark_optional (
    622           TALER_JSON_spec_econtract ("econtract",
    623                                      &rpc->econtract),
    624           &rpc->no_econtract),
    625         GNUNET_JSON_spec_fixed_auto ("merge_pub",
    626                                      &rpc->merge_pub),
    627         GNUNET_JSON_spec_fixed_auto ("merge_sig",
    628                                      &rpc->merge_sig),
    629         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    630                                      &rpc->reserve_sig),
    631         GNUNET_JSON_spec_fixed_auto ("purse_pub",
    632                                      &rpc->pd.purse_pub),
    633         GNUNET_JSON_spec_fixed_auto ("purse_sig",
    634                                      &rpc->purse_sig),
    635         GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    636                                      &rpc->pd.h_contract_terms),
    637         GNUNET_JSON_spec_timestamp ("merge_timestamp",
    638                                     &rpc->merge_timestamp),
    639         GNUNET_JSON_spec_timestamp ("purse_expiration",
    640                                     &rpc->pd.purse_expiration),
    641         GNUNET_JSON_spec_end ()
    642       };
    643 
    644       {
    645         enum GNUNET_GenericReturnValue res;
    646 
    647         res = TALER_MHD_parse_json_data (connection,
    648                                          root,
    649                                          spec);
    650         if (GNUNET_SYSERR == res)
    651         {
    652           GNUNET_break (0);
    653           return MHD_NO; /* hard failure */
    654         }
    655         if (GNUNET_NO == res)
    656         {
    657           GNUNET_break_op (0);
    658           return MHD_YES; /* failure */
    659         }
    660       }
    661     }
    662 
    663     rpc->payto_uri
    664       = TALER_reserve_make_payto (TEH_base_url,
    665                                   &rpc->account_pub.reserve_pub);
    666     TALER_normalized_payto_hash (rpc->payto_uri,
    667                                  &rpc->h_payto);
    668     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    669 
    670 
    671     if (GNUNET_OK !=
    672         TALER_wallet_purse_merge_verify (rpc->payto_uri,
    673                                          rpc->merge_timestamp,
    674                                          &rpc->pd.purse_pub,
    675                                          &rpc->merge_pub,
    676                                          &rpc->merge_sig))
    677     {
    678       MHD_RESULT ret;
    679 
    680       GNUNET_break_op (0);
    681       ret = TALER_MHD_reply_with_error (
    682         connection,
    683         MHD_HTTP_FORBIDDEN,
    684         TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID,
    685         rpc->payto_uri.normalized_payto);
    686       return ret;
    687     }
    688     GNUNET_assert (GNUNET_OK ==
    689                    TALER_amount_set_zero (TEH_currency,
    690                                           &rpc->deposit_total));
    691     if (GNUNET_TIME_timestamp_cmp (rpc->pd.purse_expiration,
    692                                    <,
    693                                    rpc->exchange_timestamp))
    694     {
    695       GNUNET_break_op (0);
    696       return TALER_MHD_reply_with_error (
    697         connection,
    698         MHD_HTTP_BAD_REQUEST,
    699         TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW,
    700         NULL);
    701     }
    702     if (GNUNET_TIME_absolute_is_never (
    703           rpc->pd.purse_expiration.abs_time))
    704     {
    705       GNUNET_break_op (0);
    706       return TALER_MHD_reply_with_error (
    707         connection,
    708         MHD_HTTP_BAD_REQUEST,
    709         TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER,
    710         NULL);
    711     }
    712 
    713     {
    714       struct TEH_KeyStateHandle *keys;
    715       const struct TEH_GlobalFee *gf;
    716 
    717       keys = TEH_keys_get_state ();
    718       if (NULL == keys)
    719       {
    720         GNUNET_break (0);
    721         return TALER_MHD_reply_with_error (
    722           connection,
    723           MHD_HTTP_INTERNAL_SERVER_ERROR,
    724           TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
    725           NULL);
    726       }
    727       gf = TEH_keys_global_fee_by_time (keys,
    728                                         rpc->exchange_timestamp);
    729       if (NULL == gf)
    730       {
    731         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    732                     "Cannot create purse: global fees not configured!\n");
    733         return TALER_MHD_reply_with_error (
    734           connection,
    735           MHD_HTTP_INTERNAL_SERVER_ERROR,
    736           TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
    737           NULL);
    738       }
    739       if (rpc->no_purse_fee)
    740       {
    741         rpc->flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
    742         GNUNET_assert (GNUNET_OK ==
    743                        TALER_amount_set_zero (TEH_currency,
    744                                               &rpc->purse_fee));
    745       }
    746       else
    747       {
    748         rpc->flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
    749         if (-1 ==
    750             TALER_amount_cmp (&rpc->purse_fee,
    751                               &gf->fees.purse))
    752         {
    753           /* rpc->purse_fee is below gf.fees.purse! */
    754           GNUNET_break_op (0);
    755           return TALER_MHD_reply_with_error (
    756             connection,
    757             MHD_HTTP_BAD_REQUEST,
    758             TALER_EC_EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW,
    759             TALER_amount2s (&gf->fees.purse));
    760         }
    761       }
    762     }
    763 
    764     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    765     if (GNUNET_OK !=
    766         TALER_wallet_purse_create_verify (
    767           rpc->pd.purse_expiration,
    768           &rpc->pd.h_contract_terms,
    769           &rpc->merge_pub,
    770           rpc->min_age,
    771           &rpc->pd.target_amount,
    772           &rpc->pd.purse_pub,
    773           &rpc->purse_sig))
    774     {
    775       GNUNET_break_op (0);
    776       return TALER_MHD_reply_with_error (
    777         connection,
    778         MHD_HTTP_FORBIDDEN,
    779         TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
    780         NULL);
    781     }
    782     if (GNUNET_OK !=
    783         TALER_wallet_account_merge_verify (
    784           rpc->merge_timestamp,
    785           &rpc->pd.purse_pub,
    786           rpc->pd.purse_expiration,
    787           &rpc->pd.h_contract_terms,
    788           &rpc->pd.target_amount,
    789           &rpc->purse_fee,
    790           rpc->min_age,
    791           rpc->flags,
    792           &rpc->account_pub.reserve_pub,
    793           &rpc->reserve_sig))
    794     {
    795       GNUNET_break_op (0);
    796       return TALER_MHD_reply_with_error (
    797         connection,
    798         MHD_HTTP_FORBIDDEN,
    799         TALER_EC_EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID,
    800         NULL);
    801     }
    802     if ( (! rpc->no_econtract) &&
    803          (GNUNET_OK !=
    804           TALER_wallet_econtract_upload_verify (
    805             rpc->econtract.econtract,
    806             rpc->econtract.econtract_size,
    807             &rpc->econtract.contract_pub,
    808             &rpc->pd.purse_pub,
    809             &rpc->econtract.econtract_sig))
    810          )
    811     {
    812       TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n");
    813       return TALER_MHD_reply_with_error (connection,
    814                                          MHD_HTTP_FORBIDDEN,
    815                                          TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
    816                                          NULL);
    817     }
    818     {
    819       struct TALER_FullPayto fake_full_payto;
    820 
    821       GNUNET_asprintf (&fake_full_payto.full_payto,
    822                        "%s?receiver-name=wallet",
    823                        rpc->payto_uri.normalized_payto);
    824       rpc->lch = TEH_legitimization_check (
    825         &rpc->rc->async_scope_id,
    826         TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
    827         fake_full_payto,
    828         &rpc->h_payto,
    829         &rpc->account_pub,
    830         &amount_iterator,
    831         rpc,
    832         &reserve_purse_legi_cb,
    833         rpc);
    834       GNUNET_free (fake_full_payto.full_payto);
    835     }
    836     GNUNET_assert (NULL != rpc->lch);
    837     MHD_suspend_connection (rc->connection);
    838     GNUNET_CONTAINER_DLL_insert (rpc_head,
    839                                  rpc_tail,
    840                                  rpc);
    841     return MHD_YES;
    842   }
    843   if (NULL != rpc->response)
    844     return MHD_queue_response (connection,
    845                                rpc->http_status,
    846                                rpc->response);
    847   if (! rpc->kyc.ok)
    848   {
    849     if (0 == rpc->kyc.requirement_row)
    850     {
    851       GNUNET_break (0);
    852       return TALER_MHD_reply_with_error (
    853         connection,
    854         MHD_HTTP_INTERNAL_SERVER_ERROR,
    855         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    856         "requirement row not set");
    857     }
    858     return TEH_RESPONSE_reply_kyc_required (
    859       connection,
    860       &rpc->h_payto,
    861       &rpc->kyc,
    862       false);
    863   }
    864 
    865   {
    866     MHD_RESULT mhd_ret;
    867 
    868     if (GNUNET_OK !=
    869         TEH_DB_run_transaction (connection,
    870                                 "execute reserve purse",
    871                                 TEH_MT_REQUEST_RESERVE_PURSE,
    872                                 &mhd_ret,
    873                                 &purse_transaction,
    874                                 rpc))
    875     {
    876       return mhd_ret;
    877     }
    878   }
    879 
    880   /* generate regular response */
    881   {
    882     MHD_RESULT res;
    883 
    884     res = TEH_RESPONSE_reply_purse_created (
    885       connection,
    886       rpc->exchange_timestamp,
    887       &rpc->deposit_total,
    888       &rpc->pd);
    889     return res;
    890   }
    891 }
    892 
    893 
    894 /* end of taler-exchange-httpd_reserves_purse.c */