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-purses-PURSE_PUB-create.c (24607B)


      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-purses-PURSE_PUB-create.c
     18  * @brief Handle /purses/$PID/create 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 <gnunet/gnunet_util_lib.h>
     24 #include <gnunet/gnunet_json_lib.h>
     25 #include <jansson.h>
     26 #include <microhttpd.h>
     27 #include "taler/taler_json_lib.h"
     28 #include "taler/taler_mhd_lib.h"
     29 #include "taler-exchange-httpd_common_deposit.h"
     30 #include "taler-exchange-httpd_post-purses-PURSE_PUB-create.h"
     31 #include "taler-exchange-httpd_responses.h"
     32 #include "exchangedb_lib.h"
     33 #include "taler-exchange-httpd_get-keys.h"
     34 #include "exchange-database/get_contract_by_purse.h"
     35 #include "exchange-database/do_purse_deposit.h"
     36 #include "exchange-database/get_purse_deposit.h"
     37 #include "exchange-database/get_purse_request.h"
     38 #include "exchange-database/insert_contract.h"
     39 #include "exchange-database/insert_purse_request.h"
     40 #include "exchange-database/preflight.h"
     41 #include "exchange-database/rollback.h"
     42 
     43 
     44 /**
     45  * Closure for #create_transaction.
     46  */
     47 struct PurseCreateContext
     48 {
     49 
     50   /**
     51    * Total actually deposited by all the coins.
     52    */
     53   struct TALER_Amount deposit_total;
     54 
     55   /**
     56    * Our current time.
     57    */
     58   struct GNUNET_TIME_Timestamp exchange_timestamp;
     59 
     60   /**
     61    * Merge key for the purse.
     62    */
     63   struct TALER_PurseMergePublicKeyP merge_pub;
     64 
     65   /**
     66    * Encrypted contract of for the purse.
     67    */
     68   struct TALER_EncryptedContract econtract;
     69 
     70   /**
     71    * Signature of the client affiming this request.
     72    */
     73   struct TALER_PurseContractSignatureP purse_sig;
     74 
     75   /**
     76    * Fundamental details about the purse.
     77    */
     78   struct TEH_PurseDetails pd;
     79 
     80   /**
     81    * Array of coins being deposited.
     82    */
     83   struct TEH_PurseDepositedCoin *coins;
     84 
     85   /**
     86    * Length of the @e coins array.
     87    */
     88   unsigned int num_coins;
     89 
     90   /**
     91    * Minimum age for deposits into this purse.
     92    */
     93   uint32_t min_age;
     94 
     95   /**
     96    * Do we have an @e econtract?
     97    */
     98   bool no_econtract;
     99 
    100 };
    101 
    102 
    103 /**
    104  * Execute database transaction for /purses/$PID/create.  Runs the transaction
    105  * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
    106  * a MHD response.  IF it returns an hard error, the transaction logic MUST
    107  * queue a MHD response and set @a mhd_ret.  IF it returns the soft error
    108  * code, the function MAY be called again to retry and MUST not queue a MHD
    109  * response.
    110  *
    111  * @param cls a `struct PurseCreateContext`
    112  * @param connection MHD request context
    113  * @param[out] mhd_ret set to MHD status on error
    114  * @return transaction status
    115  */
    116 static enum GNUNET_DB_QueryStatus
    117 create_transaction (void *cls,
    118                     struct MHD_Connection *connection,
    119                     enum MHD_Result *mhd_ret)
    120 {
    121   struct PurseCreateContext *pcc = cls;
    122   enum GNUNET_DB_QueryStatus qs;
    123   struct TALER_Amount purse_fee;
    124   bool in_conflict = true;
    125 
    126   GNUNET_assert (GNUNET_OK ==
    127                  TALER_amount_set_zero (TEH_currency,
    128                                         &purse_fee));
    129   /* 1) create purse */
    130   qs = TALER_EXCHANGEDB_insert_purse_request (
    131     TEH_pg,
    132     &pcc->pd.purse_pub,
    133     &pcc->merge_pub,
    134     pcc->pd.purse_expiration,
    135     &pcc->pd.h_contract_terms,
    136     pcc->min_age,
    137     TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
    138     &purse_fee,
    139     &pcc->pd.target_amount,
    140     &pcc->purse_sig,
    141     &in_conflict);
    142   if (qs < 0)
    143   {
    144     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    145       return qs;
    146     TALER_LOG_WARNING (
    147       "Failed to store create purse information in database\n");
    148     *mhd_ret =
    149       TALER_MHD_reply_with_error (connection,
    150                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    151                                   TALER_EC_GENERIC_DB_STORE_FAILED,
    152                                   "purse create");
    153     return GNUNET_DB_STATUS_HARD_ERROR;
    154   }
    155   if (in_conflict)
    156   {
    157     struct TALER_PurseMergePublicKeyP merge_pub;
    158     struct GNUNET_TIME_Timestamp purse_expiration;
    159     struct TALER_PrivateContractHashP h_contract_terms;
    160     struct TALER_Amount target_amount;
    161     struct TALER_Amount balance;
    162     struct TALER_PurseContractSignatureP purse_sig;
    163     uint32_t min_age;
    164 
    165     TALER_EXCHANGEDB_rollback (TEH_pg);
    166     qs = TALER_EXCHANGEDB_get_purse_request (TEH_pg,
    167                                              &pcc->pd.purse_pub,
    168                                              &merge_pub,
    169                                              &purse_expiration,
    170                                              &h_contract_terms,
    171                                              &min_age,
    172                                              &target_amount,
    173                                              &balance,
    174                                              &purse_sig);
    175     if (qs < 0)
    176     {
    177       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    178       TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
    179       *mhd_ret = TALER_MHD_reply_with_error (connection,
    180                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    181                                              TALER_EC_GENERIC_DB_FETCH_FAILED,
    182                                              "select purse request");
    183       return GNUNET_DB_STATUS_HARD_ERROR;
    184     }
    185     *mhd_ret
    186       = TALER_MHD_REPLY_JSON_PACK (
    187           connection,
    188           MHD_HTTP_CONFLICT,
    189           TALER_JSON_pack_ec (
    190             TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA),
    191           TALER_JSON_pack_amount ("amount",
    192                                   &target_amount),
    193           GNUNET_JSON_pack_uint64 ("min_age",
    194                                    min_age),
    195           GNUNET_JSON_pack_timestamp ("purse_expiration",
    196                                       purse_expiration),
    197           GNUNET_JSON_pack_data_auto ("purse_sig",
    198                                       &purse_sig),
    199           GNUNET_JSON_pack_data_auto ("h_contract_terms",
    200                                       &h_contract_terms),
    201           GNUNET_JSON_pack_data_auto ("merge_pub",
    202                                       &merge_pub));
    203     return GNUNET_DB_STATUS_HARD_ERROR;
    204   }
    205   /* 2) deposit all coins */
    206   for (unsigned int i = 0; i<pcc->num_coins; i++)
    207   {
    208     struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
    209     bool balance_ok = false;
    210     bool conflict = true;
    211     bool too_late = true;
    212 
    213     qs = TEH_make_coin_known (&coin->cpi,
    214                               connection,
    215                               &coin->known_coin_id,
    216                               mhd_ret);
    217     if (qs < 0)
    218       return qs;
    219     qs = TALER_EXCHANGEDB_do_purse_deposit (TEH_pg,
    220                                             &pcc->pd.purse_pub,
    221                                             &coin->cpi.coin_pub,
    222                                             &coin->amount,
    223                                             &coin->coin_sig,
    224                                             &coin->amount_minus_fee,
    225                                             &balance_ok,
    226                                             &too_late,
    227                                             &conflict);
    228     if (qs <= 0)
    229     {
    230       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    231         return qs;
    232       GNUNET_break (0 != qs);
    233       TALER_LOG_WARNING (
    234         "Failed to store purse deposit information in database\n");
    235       *mhd_ret = TALER_MHD_reply_with_error (connection,
    236                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    237                                              TALER_EC_GENERIC_DB_STORE_FAILED,
    238                                              "purse create deposit");
    239       return GNUNET_DB_STATUS_HARD_ERROR;
    240     }
    241     if (! balance_ok)
    242     {
    243       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    244                   "Coin %s has insufficient balance for purse deposit of amount %s\n",
    245                   TALER_B2S (&coin->cpi.coin_pub),
    246                   TALER_amount2s (&coin->amount));
    247       *mhd_ret
    248         = TEH_RESPONSE_reply_coin_insufficient_funds (
    249             connection,
    250             TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
    251             &coin->cpi.denom_pub_hash,
    252             &coin->cpi.coin_pub);
    253       return GNUNET_DB_STATUS_HARD_ERROR;
    254     }
    255     if (too_late)
    256     {
    257       *mhd_ret
    258         = TALER_MHD_reply_with_ec (
    259             connection,
    260             TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    261             "too late to deposit on purse creation");
    262       return GNUNET_DB_STATUS_HARD_ERROR;
    263     }
    264     if (conflict)
    265     {
    266       struct TALER_Amount amount;
    267       struct TALER_CoinSpendSignatureP coin_sig;
    268       struct TALER_DenominationHashP h_denom_pub;
    269       struct TALER_AgeCommitmentHashP hac;
    270       const struct TALER_AgeCommitmentHashP *phac = NULL;
    271       bool no_age_commitment;
    272       char *partner_url = NULL;
    273 
    274       TALER_EXCHANGEDB_rollback (TEH_pg);
    275       qs = TALER_EXCHANGEDB_get_purse_deposit (TEH_pg,
    276                                                &pcc->pd.purse_pub,
    277                                                &coin->cpi.coin_pub,
    278                                                &amount,
    279                                                &h_denom_pub,
    280                                                &hac,
    281                                                &no_age_commitment,
    282                                                &coin_sig,
    283                                                &partner_url);
    284       if (qs < 0)
    285       {
    286         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    287         TALER_LOG_WARNING (
    288           "Failed to fetch purse deposit information from database\n");
    289         *mhd_ret = TALER_MHD_reply_with_error (connection,
    290                                                MHD_HTTP_INTERNAL_SERVER_ERROR,
    291                                                TALER_EC_GENERIC_DB_FETCH_FAILED,
    292                                                "get purse deposit");
    293         return GNUNET_DB_STATUS_HARD_ERROR;
    294       }
    295       if (! no_age_commitment)
    296         phac = &hac;
    297 
    298       *mhd_ret
    299         = TALER_MHD_REPLY_JSON_PACK (
    300             connection,
    301             MHD_HTTP_CONFLICT,
    302             TALER_JSON_pack_ec (
    303               TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA),
    304             GNUNET_JSON_pack_data_auto ("coin_pub",
    305                                         &coin->cpi.coin_pub),
    306             GNUNET_JSON_pack_data_auto ("coin_sig",
    307                                         &coin_sig),
    308             GNUNET_JSON_pack_data_auto ("h_denom_pub",
    309                                         &h_denom_pub),
    310             GNUNET_JSON_pack_allow_null (
    311               GNUNET_JSON_pack_data_auto ("h_age_restrictions",
    312                                           phac)),
    313             GNUNET_JSON_pack_allow_null (
    314               GNUNET_JSON_pack_string ("partner_url",
    315                                        partner_url)),
    316             TALER_JSON_pack_amount ("amount",
    317                                     &amount));
    318       GNUNET_free (partner_url);
    319       return GNUNET_DB_STATUS_HARD_ERROR;
    320     }
    321   }
    322   /* 3) if present, persist contract */
    323   if (! pcc->no_econtract)
    324   {
    325     in_conflict = true;
    326     qs = TALER_EXCHANGEDB_insert_contract (TEH_pg,
    327                                            &pcc->pd.purse_pub,
    328                                            &pcc->econtract,
    329                                            &in_conflict);
    330     if (qs < 0)
    331     {
    332       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    333         return qs;
    334       TALER_LOG_WARNING ("Failed to store purse information in database\n");
    335       *mhd_ret = TALER_MHD_reply_with_error (connection,
    336                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    337                                              TALER_EC_GENERIC_DB_STORE_FAILED,
    338                                              "purse create contract");
    339       return GNUNET_DB_STATUS_HARD_ERROR;
    340     }
    341     if (in_conflict)
    342     {
    343       struct TALER_EncryptedContract econtract;
    344       struct GNUNET_HashCode h_econtract;
    345 
    346       qs = TALER_EXCHANGEDB_get_contract_by_purse (
    347         TEH_pg,
    348         &pcc->pd.purse_pub,
    349         &econtract);
    350       if (qs <= 0)
    351       {
    352         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    353           return qs;
    354         GNUNET_break (0 != qs);
    355         TALER_LOG_WARNING (
    356           "Failed to store fetch contract information from database\n");
    357         *mhd_ret = TALER_MHD_reply_with_error (connection,
    358                                                MHD_HTTP_INTERNAL_SERVER_ERROR,
    359                                                TALER_EC_GENERIC_DB_FETCH_FAILED,
    360                                                "select contract");
    361         return GNUNET_DB_STATUS_HARD_ERROR;
    362       }
    363       GNUNET_CRYPTO_hash (econtract.econtract,
    364                           econtract.econtract_size,
    365                           &h_econtract);
    366       *mhd_ret
    367         = TALER_MHD_REPLY_JSON_PACK (
    368             connection,
    369             MHD_HTTP_CONFLICT,
    370             TALER_JSON_pack_ec (
    371               TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
    372             GNUNET_JSON_pack_data_auto ("h_econtract",
    373                                         &h_econtract),
    374             GNUNET_JSON_pack_data_auto ("econtract_sig",
    375                                         &econtract.econtract_sig),
    376             GNUNET_JSON_pack_data_auto ("contract_pub",
    377                                         &econtract.contract_pub));
    378       GNUNET_free (econtract.econtract);
    379       return GNUNET_DB_STATUS_HARD_ERROR;
    380     }
    381   }
    382   return qs;
    383 }
    384 
    385 
    386 /**
    387  * Parse a coin and check signature of the coin and the denomination
    388  * signature over the coin.
    389  *
    390  * @param[in,out] connection our HTTP connection
    391  * @param[in,out] pcc request context
    392  * @param[out] coin coin to initialize
    393  * @param jcoin coin to parse
    394  * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
    395  *         #GNUNET_SYSERR on failure and no error could be returned
    396  */
    397 static enum GNUNET_GenericReturnValue
    398 parse_coin (struct MHD_Connection *connection,
    399             struct PurseCreateContext *pcc,
    400             struct TEH_PurseDepositedCoin *coin,
    401             const json_t *jcoin)
    402 {
    403   enum GNUNET_GenericReturnValue iret;
    404 
    405   if (GNUNET_OK !=
    406       (iret = TEH_common_purse_deposit_parse_coin (connection,
    407                                                    coin,
    408                                                    jcoin)))
    409     return iret;
    410   if (GNUNET_OK !=
    411       (iret = TEH_common_deposit_check_purse_deposit (
    412          connection,
    413          coin,
    414          &pcc->pd.purse_pub,
    415          pcc->min_age)))
    416     return iret;
    417   if (0 >
    418       TALER_amount_add (&pcc->deposit_total,
    419                         &pcc->deposit_total,
    420                         &coin->amount_minus_fee))
    421   {
    422     GNUNET_break (0);
    423     return (MHD_YES ==
    424             TALER_MHD_reply_with_error (connection,
    425                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    426                                         TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
    427                                         "total deposit contribution"))
    428            ? GNUNET_NO
    429            : GNUNET_SYSERR;
    430   }
    431   return GNUNET_OK;
    432 }
    433 
    434 
    435 enum MHD_Result
    436 TEH_handler_purses_create (
    437   struct TEH_RequestContext *rc,
    438   const struct TALER_PurseContractPublicKeyP *purse_pub,
    439   const json_t *root)
    440 {
    441   struct MHD_Connection *connection = rc->connection;
    442   struct PurseCreateContext pcc = {
    443     .pd.purse_pub = *purse_pub,
    444     .exchange_timestamp = GNUNET_TIME_timestamp_get ()
    445   };
    446   const json_t *deposits;
    447   json_t *deposit;
    448   unsigned int idx;
    449   struct GNUNET_JSON_Specification spec[] = {
    450     TALER_JSON_spec_amount ("amount",
    451                             TEH_currency,
    452                             &pcc.pd.target_amount),
    453     GNUNET_JSON_spec_uint32 ("min_age",
    454                              &pcc.min_age),
    455     GNUNET_JSON_spec_mark_optional (
    456       TALER_JSON_spec_econtract ("econtract",
    457                                  &pcc.econtract),
    458       &pcc.no_econtract),
    459     GNUNET_JSON_spec_fixed_auto ("merge_pub",
    460                                  &pcc.merge_pub),
    461     GNUNET_JSON_spec_fixed_auto ("purse_sig",
    462                                  &pcc.purse_sig),
    463     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    464                                  &pcc.pd.h_contract_terms),
    465     GNUNET_JSON_spec_array_const ("deposits",
    466                                   &deposits),
    467     GNUNET_JSON_spec_timestamp ("purse_expiration",
    468                                 &pcc.pd.purse_expiration),
    469     GNUNET_JSON_spec_end ()
    470   };
    471   const struct TEH_GlobalFee *gf;
    472 
    473   {
    474     enum GNUNET_GenericReturnValue res;
    475 
    476     res = TALER_MHD_parse_json_data (connection,
    477                                      root,
    478                                      spec);
    479     if (GNUNET_SYSERR == res)
    480     {
    481       GNUNET_break (0);
    482       return MHD_NO; /* hard failure */
    483     }
    484     if (GNUNET_NO == res)
    485     {
    486       GNUNET_break_op (0);
    487       return MHD_YES; /* failure */
    488     }
    489   }
    490   GNUNET_assert (GNUNET_OK ==
    491                  TALER_amount_set_zero (TEH_currency,
    492                                         &pcc.deposit_total));
    493   if (GNUNET_TIME_timestamp_cmp (pcc.pd.purse_expiration,
    494                                  <,
    495                                  pcc.exchange_timestamp))
    496   {
    497     GNUNET_break_op (0);
    498     GNUNET_JSON_parse_free (spec);
    499     return TALER_MHD_reply_with_error (connection,
    500                                        MHD_HTTP_BAD_REQUEST,
    501                                        TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW,
    502                                        NULL);
    503   }
    504   if (GNUNET_TIME_absolute_is_never (pcc.pd.purse_expiration.abs_time))
    505   {
    506     GNUNET_break_op (0);
    507     GNUNET_JSON_parse_free (spec);
    508     return TALER_MHD_reply_with_error (connection,
    509                                        MHD_HTTP_BAD_REQUEST,
    510                                        TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER,
    511                                        NULL);
    512   }
    513   pcc.num_coins = json_array_size (deposits);
    514   if ( (0 == pcc.num_coins) ||
    515        (pcc.num_coins > TALER_MAX_COINS) )
    516   {
    517     GNUNET_break_op (0);
    518     GNUNET_JSON_parse_free (spec);
    519     return TALER_MHD_reply_with_error (connection,
    520                                        MHD_HTTP_BAD_REQUEST,
    521                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    522                                        "deposits");
    523   }
    524   {
    525     struct TEH_KeyStateHandle *keys;
    526 
    527     keys = TEH_keys_get_state ();
    528     if (NULL == keys)
    529     {
    530       GNUNET_break (0);
    531       GNUNET_JSON_parse_free (spec);
    532       return TALER_MHD_reply_with_error (connection,
    533                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    534                                          TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
    535                                          NULL);
    536     }
    537     gf = TEH_keys_global_fee_by_time (keys,
    538                                       pcc.exchange_timestamp);
    539   }
    540   if (NULL == gf)
    541   {
    542     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    543                 "Cannot create purse: global fees not configured!\n");
    544     GNUNET_JSON_parse_free (spec);
    545     return TALER_MHD_reply_with_error (connection,
    546                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    547                                        TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
    548                                        NULL);
    549   }
    550   /* parse deposits */
    551   pcc.coins = GNUNET_new_array (pcc.num_coins,
    552                                 struct TEH_PurseDepositedCoin);
    553   json_array_foreach (deposits, idx, deposit)
    554   {
    555     enum GNUNET_GenericReturnValue res;
    556     struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx];
    557 
    558     res = parse_coin (connection,
    559                       &pcc,
    560                       coin,
    561                       deposit);
    562     if (GNUNET_OK != res)
    563     {
    564       /* Note: coins[idx] may have been fully parsed before the
    565          check failed, so it must be released as well. */
    566       for (unsigned int i = 0; i<=idx; i++)
    567         TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    568       GNUNET_free (pcc.coins);
    569       GNUNET_JSON_parse_free (spec);
    570       return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
    571     }
    572   }
    573 
    574   if (0 < TALER_amount_cmp (&gf->fees.purse,
    575                             &pcc.deposit_total))
    576   {
    577     GNUNET_break_op (0);
    578     for (unsigned int i = 0; i<pcc.num_coins; i++)
    579       TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    580     GNUNET_free (pcc.coins);
    581     GNUNET_JSON_parse_free (spec);
    582     return TALER_MHD_reply_with_error (connection,
    583                                        MHD_HTTP_BAD_REQUEST,
    584                                        TALER_EC_EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE,
    585                                        NULL);
    586   }
    587   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    588 
    589   if (GNUNET_OK !=
    590       TALER_wallet_purse_create_verify (
    591         pcc.pd.purse_expiration,
    592         &pcc.pd.h_contract_terms,
    593         &pcc.merge_pub,
    594         pcc.min_age,
    595         &pcc.pd.target_amount,
    596         &pcc.pd.purse_pub,
    597         &pcc.purse_sig))
    598   {
    599     TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
    600     for (unsigned int i = 0; i<pcc.num_coins; i++)
    601       TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    602     GNUNET_free (pcc.coins);
    603     GNUNET_JSON_parse_free (spec);
    604     return TALER_MHD_reply_with_error (connection,
    605                                        MHD_HTTP_FORBIDDEN,
    606                                        TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
    607                                        NULL);
    608   }
    609   if ( (! pcc.no_econtract) &&
    610        (GNUNET_OK !=
    611         TALER_wallet_econtract_upload_verify (pcc.econtract.econtract,
    612                                               pcc.econtract.econtract_size,
    613                                               &pcc.econtract.contract_pub,
    614                                               purse_pub,
    615                                               &pcc.econtract.econtract_sig)) )
    616   {
    617     TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
    618     for (unsigned int i = 0; i<pcc.num_coins; i++)
    619       TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    620     GNUNET_free (pcc.coins);
    621     GNUNET_JSON_parse_free (spec);
    622     return TALER_MHD_reply_with_error (connection,
    623                                        MHD_HTTP_FORBIDDEN,
    624                                        TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
    625                                        NULL);
    626   }
    627 
    628 
    629   if (GNUNET_SYSERR ==
    630       TALER_EXCHANGEDB_preflight (TEH_pg))
    631   {
    632     GNUNET_break (0);
    633     for (unsigned int i = 0; i<pcc.num_coins; i++)
    634       TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    635     GNUNET_free (pcc.coins);
    636     GNUNET_JSON_parse_free (spec);
    637     return TALER_MHD_reply_with_error (connection,
    638                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    639                                        TALER_EC_GENERIC_DB_START_FAILED,
    640                                        "preflight failure");
    641   }
    642 
    643   /* execute transaction */
    644   {
    645     enum MHD_Result mhd_ret;
    646 
    647     if (GNUNET_OK !=
    648         TEH_DB_run_transaction (connection,
    649                                 "execute purse create",
    650                                 TEH_MT_REQUEST_PURSE_CREATE,
    651                                 &mhd_ret,
    652                                 &create_transaction,
    653                                 &pcc))
    654     {
    655       for (unsigned int i = 0; i<pcc.num_coins; i++)
    656         TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    657       GNUNET_free (pcc.coins);
    658       GNUNET_JSON_parse_free (spec);
    659       return mhd_ret;
    660     }
    661   }
    662 
    663   /* generate regular response */
    664   {
    665     enum MHD_Result res;
    666 
    667     res = TEH_RESPONSE_reply_purse_created (connection,
    668                                             pcc.exchange_timestamp,
    669                                             &pcc.deposit_total,
    670                                             &pcc.pd);
    671     for (unsigned int i = 0; i<pcc.num_coins; i++)
    672       TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    673     GNUNET_free (pcc.coins);
    674     GNUNET_JSON_parse_free (spec);
    675     return res;
    676   }
    677 }
    678 
    679 
    680 /* end of taler-exchange-httpd_purses_create.c */