LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_purses_deposit.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 61.8 % 136 84
Test Date: 2025-12-26 23:00:34 Functions: 100.0 % 4 4

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

Generated by: LCOV version 2.0-1