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-deposit.c (19036B)


      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-deposit.c
     18  * @brief Handle /purses/$PID/deposit 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 "platform.h"  /* UNNECESSARY? */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <jansson.h>
     27 #include <microhttpd.h>
     28 #include "taler/taler_dbevents.h"  /* UNNECESSARY? */
     29 #include "taler/taler_json_lib.h"
     30 #include "taler/taler_mhd_lib.h"
     31 #include "taler-exchange-httpd_common_deposit.h"
     32 #include "taler-exchange-httpd_post-purses-PURSE_PUB-deposit.h"
     33 #include "taler-exchange-httpd_responses.h"
     34 #include "exchangedb_lib.h"
     35 #include "taler-exchange-httpd_get-keys.h"
     36 #include "exchange-database/get_purse.h"
     37 #include "exchange-database/get_purse_request.h"
     38 #include "exchange-database/do_purse_deposit.h"
     39 #include "exchange-database/event_notify.h"
     40 #include "exchange-database/get_purse_deposit.h"
     41 #include "exchange-database/preflight.h"
     42 #include "exchange-database/rollback.h"
     43 
     44 
     45 /**
     46  * Closure for #deposit_transaction.
     47  */
     48 struct PurseDepositContext
     49 {
     50   /**
     51    * Public key of the purse we are creating.
     52    */
     53   const struct TALER_PurseContractPublicKeyP *purse_pub;
     54 
     55   /**
     56    * Total amount to be put into the purse.
     57    */
     58   struct TALER_Amount amount;
     59 
     60   /**
     61    * Total actually deposited by all the coins.
     62    */
     63   struct TALER_Amount deposit_total;
     64 
     65   /**
     66    * When should the purse expire.
     67    */
     68   struct GNUNET_TIME_Timestamp purse_expiration;
     69 
     70   /**
     71    * Hash of the contract (needed for signing).
     72    */
     73   struct TALER_PrivateContractHashP h_contract_terms;
     74 
     75   /**
     76    * Our current time.
     77    */
     78   struct GNUNET_TIME_Timestamp exchange_timestamp;
     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 
     97 /**
     98  * Send confirmation of purse creation success to client.
     99  *
    100  * @param connection connection to the client
    101  * @param pcc details about the request that succeeded
    102  * @return MHD result code
    103  */
    104 static enum MHD_Result
    105 reply_deposit_success (struct MHD_Connection *connection,
    106                        const struct PurseDepositContext *pcc)
    107 {
    108   struct TALER_ExchangePublicKeyP pub;
    109   struct TALER_ExchangeSignatureP sig;
    110   enum TALER_ErrorCode ec;
    111 
    112   if (TALER_EC_NONE !=
    113       (ec = TALER_exchange_online_purse_created_sign (
    114          &TEH_keys_exchange_sign_,
    115          pcc->exchange_timestamp,
    116          pcc->purse_expiration,
    117          &pcc->amount,
    118          &pcc->deposit_total,
    119          pcc->purse_pub,
    120          &pcc->h_contract_terms,
    121          &pub,
    122          &sig)))
    123   {
    124     GNUNET_break (0);
    125     return TALER_MHD_reply_with_ec (connection,
    126                                     ec,
    127                                     NULL);
    128   }
    129   return TALER_MHD_REPLY_JSON_PACK (
    130     connection,
    131     MHD_HTTP_OK,
    132     TALER_JSON_pack_amount ("total_deposited",
    133                             &pcc->deposit_total),
    134     TALER_JSON_pack_amount ("purse_value_after_fees",
    135                             &pcc->amount),
    136     GNUNET_JSON_pack_timestamp ("exchange_timestamp",
    137                                 pcc->exchange_timestamp),
    138     GNUNET_JSON_pack_timestamp ("purse_expiration",
    139                                 pcc->purse_expiration),
    140     GNUNET_JSON_pack_data_auto ("h_contract_terms",
    141                                 &pcc->h_contract_terms),
    142     GNUNET_JSON_pack_data_auto ("exchange_sig",
    143                                 &sig),
    144     GNUNET_JSON_pack_data_auto ("exchange_pub",
    145                                 &pub));
    146 }
    147 
    148 
    149 /**
    150  * Execute database transaction for /purses/$PID/deposit.  Runs the transaction
    151  * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
    152  * a MHD response.  IF it returns an hard error, the transaction logic MUST
    153  * queue a MHD response and set @a mhd_ret.  IF it returns the soft error
    154  * code, the function MAY be called again to retry and MUST not queue a MHD
    155  * response.
    156  *
    157  * @param cls a `struct PurseDepositContext`
    158  * @param connection MHD request context
    159  * @param[out] mhd_ret set to MHD status on error
    160  * @return transaction status
    161  */
    162 static enum GNUNET_DB_QueryStatus
    163 deposit_transaction (void *cls,
    164                      struct MHD_Connection *connection,
    165                      enum MHD_Result *mhd_ret)
    166 {
    167   struct PurseDepositContext *pcc = cls;
    168   enum GNUNET_DB_QueryStatus qs;
    169 
    170   qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    171   for (unsigned int i = 0; i<pcc->num_coins; i++)
    172   {
    173     struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
    174     bool balance_ok = false;
    175     bool conflict = true;
    176     bool too_late = true;
    177 
    178     qs = TEH_make_coin_known (&coin->cpi,
    179                               connection,
    180                               &coin->known_coin_id,
    181                               mhd_ret);
    182     if (qs < 0)
    183       return qs;
    184     qs = TALER_EXCHANGEDB_do_purse_deposit (TEH_pg,
    185                                             pcc->purse_pub,
    186                                             &coin->cpi.coin_pub,
    187                                             &coin->amount,
    188                                             &coin->coin_sig,
    189                                             &coin->amount_minus_fee,
    190                                             &balance_ok,
    191                                             &too_late,
    192                                             &conflict);
    193     if (qs <= 0)
    194     {
    195       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    196         return qs;
    197       GNUNET_break (0 != qs);
    198       TALER_LOG_WARNING (
    199         "Failed to store purse deposit information in database\n");
    200       *mhd_ret = TALER_MHD_reply_with_error (connection,
    201                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    202                                              TALER_EC_GENERIC_DB_STORE_FAILED,
    203                                              "do purse deposit");
    204       return GNUNET_DB_STATUS_HARD_ERROR;
    205     }
    206     if (! balance_ok)
    207     {
    208       *mhd_ret
    209         = TEH_RESPONSE_reply_coin_insufficient_funds (
    210             connection,
    211             TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
    212             &coin->cpi.denom_pub_hash,
    213             &coin->cpi.coin_pub);
    214       return GNUNET_DB_STATUS_HARD_ERROR;
    215     }
    216     if (too_late)
    217     {
    218       TALER_EXCHANGEDB_rollback (TEH_pg);
    219       *mhd_ret
    220         = TALER_MHD_reply_with_ec (
    221             connection,
    222             TALER_EC_EXCHANGE_PURSE_DEPOSIT_DECIDED_ALREADY,
    223             NULL);
    224       return GNUNET_DB_STATUS_HARD_ERROR;
    225     }
    226     if (conflict)
    227     {
    228       struct TALER_Amount amount;
    229       struct TALER_CoinSpendSignatureP coin_sig;
    230       struct TALER_DenominationHashP h_denom_pub;
    231       struct TALER_AgeCommitmentHashP hac;
    232       const struct TALER_AgeCommitmentHashP *phac = NULL;
    233       bool no_age_commitment;
    234       char *partner_url = NULL;
    235 
    236       TALER_EXCHANGEDB_rollback (TEH_pg);
    237       qs = TALER_EXCHANGEDB_get_purse_deposit (TEH_pg,
    238                                                pcc->purse_pub,
    239                                                &coin->cpi.coin_pub,
    240                                                &amount,
    241                                                &h_denom_pub,
    242                                                &hac,
    243                                                &no_age_commitment,
    244                                                &coin_sig,
    245                                                &partner_url);
    246       if (qs < 0)
    247       {
    248         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    249         TALER_LOG_WARNING (
    250           "Failed to fetch purse deposit information from database\n");
    251         *mhd_ret = TALER_MHD_reply_with_error (connection,
    252                                                MHD_HTTP_INTERNAL_SERVER_ERROR,
    253                                                TALER_EC_GENERIC_DB_FETCH_FAILED,
    254                                                "get purse deposit");
    255         return GNUNET_DB_STATUS_HARD_ERROR;
    256       }
    257       if (! no_age_commitment)
    258         phac = &hac;
    259 
    260       *mhd_ret
    261         = TALER_MHD_REPLY_JSON_PACK (
    262             connection,
    263             MHD_HTTP_CONFLICT,
    264             TALER_JSON_pack_ec (
    265               TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA),
    266             GNUNET_JSON_pack_data_auto ("coin_pub",
    267                                         &coin->cpi.coin_pub),
    268             GNUNET_JSON_pack_data_auto ("h_denom_pub",
    269                                         &h_denom_pub),
    270             GNUNET_JSON_pack_allow_null (
    271               GNUNET_JSON_pack_data_auto ("h_age_commitment",
    272                                           phac)),
    273             GNUNET_JSON_pack_data_auto ("coin_sig",
    274                                         &coin_sig),
    275             GNUNET_JSON_pack_allow_null (
    276               GNUNET_JSON_pack_string ("partner_url",
    277                                        partner_url)),
    278             TALER_JSON_pack_amount ("amount",
    279                                     &amount));
    280       GNUNET_free (partner_url);
    281       return GNUNET_DB_STATUS_HARD_ERROR;
    282     }
    283   }
    284   return qs;
    285 }
    286 
    287 
    288 /**
    289  * Parse a coin and check signature of the coin and the denomination
    290  * signature over the coin.
    291  *
    292  * @param[in,out] connection our HTTP connection
    293  * @param[in,out] pcc request context
    294  * @param[out] coin coin to initialize
    295  * @param jcoin coin to parse
    296  * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
    297  *         #GNUNET_SYSERR on failure and no error could be returned
    298  */
    299 static enum GNUNET_GenericReturnValue
    300 parse_coin (struct MHD_Connection *connection,
    301             struct PurseDepositContext *pcc,
    302             struct TEH_PurseDepositedCoin *coin,
    303             const json_t *jcoin)
    304 {
    305   enum GNUNET_GenericReturnValue iret;
    306 
    307   if (GNUNET_OK !=
    308       (iret = TEH_common_purse_deposit_parse_coin (connection,
    309                                                    coin,
    310                                                    jcoin)))
    311     return iret;
    312   if (GNUNET_OK !=
    313       (iret = TEH_common_deposit_check_purse_deposit (
    314          connection,
    315          coin,
    316          pcc->purse_pub,
    317          pcc->min_age)))
    318     return iret;
    319   if (0 >
    320       TALER_amount_add (&pcc->deposit_total,
    321                         &pcc->deposit_total,
    322                         &coin->amount_minus_fee))
    323   {
    324     GNUNET_break (0);
    325     return (MHD_YES ==
    326             TALER_MHD_reply_with_error (connection,
    327                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    328                                         TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
    329                                         "total deposit contribution"))
    330            ? GNUNET_NO
    331            : GNUNET_SYSERR;
    332   }
    333   return GNUNET_OK;
    334 }
    335 
    336 
    337 enum MHD_Result
    338 TEH_handler_purses_deposit (
    339   struct TEH_RequestContext *rc,
    340   const struct TALER_PurseContractPublicKeyP *purse_pub,
    341   const json_t *root)
    342 {
    343   struct MHD_Connection *connection = rc->connection;
    344   struct PurseDepositContext pcc = {
    345     .purse_pub = purse_pub,
    346     .exchange_timestamp = GNUNET_TIME_timestamp_get ()
    347   };
    348   const json_t *deposits;
    349   json_t *deposit;
    350   unsigned int idx;
    351   struct GNUNET_JSON_Specification spec[] = {
    352     GNUNET_JSON_spec_array_const ("deposits",
    353                                   &deposits),
    354     GNUNET_JSON_spec_end ()
    355   };
    356 
    357   {
    358     enum GNUNET_GenericReturnValue res;
    359 
    360     res = TALER_MHD_parse_json_data (connection,
    361                                      root,
    362                                      spec);
    363     if (GNUNET_SYSERR == res)
    364     {
    365       GNUNET_break (0);
    366       return MHD_NO; /* hard failure */
    367     }
    368     if (GNUNET_NO == res)
    369     {
    370       GNUNET_break_op (0);
    371       return MHD_YES; /* failure */
    372     }
    373   }
    374   GNUNET_assert (GNUNET_OK ==
    375                  TALER_amount_set_zero (TEH_currency,
    376                                         &pcc.deposit_total));
    377   pcc.num_coins = (unsigned int) json_array_size (deposits);
    378   if ( (0 == pcc.num_coins) ||
    379        (((size_t) pcc.num_coins) != json_array_size (deposits)) ||
    380        (pcc.num_coins > TALER_MAX_COINS) )
    381   {
    382     GNUNET_break_op (0);
    383     return TALER_MHD_reply_with_error (connection,
    384                                        MHD_HTTP_BAD_REQUEST,
    385                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    386                                        "deposits");
    387   }
    388 
    389   {
    390     enum GNUNET_DB_QueryStatus qs;
    391     struct GNUNET_TIME_Timestamp create_timestamp;
    392     struct GNUNET_TIME_Timestamp merge_timestamp;
    393     bool was_deleted;
    394     bool was_refunded;
    395 
    396     qs = TALER_EXCHANGEDB_get_purse (
    397       TEH_pg,
    398       pcc.purse_pub,
    399       &create_timestamp,
    400       &pcc.purse_expiration,
    401       &pcc.amount,
    402       &pcc.deposit_total,
    403       &pcc.h_contract_terms,
    404       &merge_timestamp,
    405       &was_deleted,
    406       &was_refunded);
    407     switch (qs)
    408     {
    409     case GNUNET_DB_STATUS_HARD_ERROR:
    410       GNUNET_break (0);
    411       return TALER_MHD_reply_with_error (connection,
    412                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    413                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    414                                          "select purse");
    415     case GNUNET_DB_STATUS_SOFT_ERROR:
    416       GNUNET_break (0);
    417       return TALER_MHD_reply_with_error (connection,
    418                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    419                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    420                                          "select purse");
    421     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    422       return TALER_MHD_reply_with_error (connection,
    423                                          MHD_HTTP_NOT_FOUND,
    424                                          TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
    425                                          NULL);
    426     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    427       break; /* handled below */
    428     }
    429     if (was_refunded ||
    430         was_deleted)
    431     {
    432       return TALER_MHD_reply_with_error (
    433         connection,
    434         MHD_HTTP_GONE,
    435         was_deleted
    436         ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
    437         : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
    438         GNUNET_TIME_timestamp2s (pcc.purse_expiration));
    439     }
    440   }
    441 
    442   /* Obtain the minimum age required for deposits into this purse;
    443      #TALER_EXCHANGEDB_get_purse() does not return it, but we must
    444      have it before we check the coins below. */
    445   {
    446     enum GNUNET_DB_QueryStatus qs;
    447     struct TALER_PurseMergePublicKeyP merge_pub;
    448     struct GNUNET_TIME_Timestamp purse_expiration;
    449     struct TALER_PrivateContractHashP h_contract_terms;
    450     struct TALER_Amount target_amount;
    451     struct TALER_Amount balance;
    452     struct TALER_PurseContractSignatureP purse_sig;
    453 
    454     qs = TALER_EXCHANGEDB_get_purse_request (
    455       TEH_pg,
    456       pcc.purse_pub,
    457       &merge_pub,
    458       &purse_expiration,
    459       &h_contract_terms,
    460       &pcc.min_age,
    461       &target_amount,
    462       &balance,
    463       &purse_sig);
    464     switch (qs)
    465     {
    466     case GNUNET_DB_STATUS_HARD_ERROR:
    467     case GNUNET_DB_STATUS_SOFT_ERROR:
    468       GNUNET_break (0);
    469       return TALER_MHD_reply_with_error (connection,
    470                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    471                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    472                                          "select purse request");
    473     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    474       return TALER_MHD_reply_with_error (connection,
    475                                          MHD_HTTP_NOT_FOUND,
    476                                          TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
    477                                          NULL);
    478     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    479       break;
    480     }
    481   }
    482 
    483   /* parse deposits */
    484   pcc.coins = GNUNET_new_array (pcc.num_coins,
    485                                 struct TEH_PurseDepositedCoin);
    486   json_array_foreach (deposits, idx, deposit)
    487   {
    488     enum GNUNET_GenericReturnValue res;
    489     struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx];
    490 
    491     res = parse_coin (connection,
    492                       &pcc,
    493                       coin,
    494                       deposit);
    495     if (GNUNET_OK != res)
    496     {
    497       /* Note: coins[idx] may have been fully parsed before the
    498          check failed, so it must be released as well. */
    499       for (unsigned int i = 0; i<=idx; i++)
    500         TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    501       GNUNET_free (pcc.coins);
    502       return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
    503     }
    504   }
    505 
    506   if (GNUNET_SYSERR ==
    507       TALER_EXCHANGEDB_preflight (TEH_pg))
    508   {
    509     GNUNET_break (0);
    510     for (unsigned int i = 0; i<pcc.num_coins; i++)
    511       TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    512     GNUNET_free (pcc.coins);
    513     return TALER_MHD_reply_with_error (connection,
    514                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    515                                        TALER_EC_GENERIC_DB_START_FAILED,
    516                                        "preflight failure");
    517   }
    518 
    519   /* execute transaction */
    520   {
    521     enum MHD_Result mhd_ret;
    522 
    523     if (GNUNET_OK !=
    524         TEH_DB_run_transaction (connection,
    525                                 "execute purse deposit",
    526                                 TEH_MT_REQUEST_PURSE_DEPOSIT,
    527                                 &mhd_ret,
    528                                 &deposit_transaction,
    529                                 &pcc))
    530     {
    531       for (unsigned int i = 0; i<pcc.num_coins; i++)
    532         TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    533       GNUNET_free (pcc.coins);
    534       return mhd_ret;
    535     }
    536   }
    537   {
    538     struct TALER_EXCHANGEDB_PurseEventP rep = {
    539       .header.size = htons (sizeof (rep)),
    540       .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
    541       .purse_pub = *pcc.purse_pub
    542     };
    543 
    544     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    545                 "Notifying about purse deposit %s\n",
    546                 TALER_B2S (pcc.purse_pub));
    547     TALER_EXCHANGEDB_event_notify (TEH_pg,
    548                                    &rep.header,
    549                                    NULL,
    550                                    0);
    551   }
    552 
    553   /* generate regular response */
    554   {
    555     enum MHD_Result res;
    556 
    557     res = reply_deposit_success (connection,
    558                                  &pcc);
    559     for (unsigned int i = 0; i<pcc.num_coins; i++)
    560       TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
    561     GNUNET_free (pcc.coins);
    562     return res;
    563   }
    564 }
    565 
    566 
    567 /* end of taler-exchange-httpd_purses_deposit.c */