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_history.c (21559B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-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_history.c
     18  * @brief Handle /reserves/$RESERVE_PUB HISTORY requests
     19  * @author Florian Dold
     20  * @author Benedikt Mueller
     21  * @author Christian Grothoff
     22  */
     23 #include "taler/platform.h"
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <jansson.h>
     26 #include "taler/taler_json_lib.h"
     27 #include "taler/taler_dbevents.h"
     28 #include "taler-exchange-httpd_keys.h"
     29 #include "taler-exchange-httpd_reserves_history.h"
     30 #include "taler-exchange-httpd_responses.h"
     31 
     32 
     33 /**
     34  * Compile the history of a reserve into a JSON object.
     35  *
     36  * @param rh reserve history to JSON-ify
     37  * @return json representation of the @a rh, NULL on error
     38  */
     39 static json_t *
     40 compile_reserve_history (
     41   const struct TALER_EXCHANGEDB_ReserveHistory *rh)
     42 {
     43   json_t *json_history;
     44 
     45   json_history = json_array ();
     46   GNUNET_assert (NULL != json_history);
     47   for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
     48        NULL != pos;
     49        pos = pos->next)
     50   {
     51     switch (pos->type)
     52     {
     53     case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
     54       {
     55         const struct TALER_EXCHANGEDB_BankTransfer *bank =
     56           pos->details.bank;
     57 
     58         if (0 !=
     59             json_array_append_new (
     60               json_history,
     61               GNUNET_JSON_PACK (
     62                 GNUNET_JSON_pack_string ("type",
     63                                          "CREDIT"),
     64                 GNUNET_JSON_pack_timestamp ("timestamp",
     65                                             bank->execution_date),
     66                 TALER_JSON_pack_full_payto ("sender_account_url",
     67                                             bank->sender_account_details),
     68                 GNUNET_JSON_pack_uint64 ("wire_reference",
     69                                          bank->wire_reference),
     70                 TALER_JSON_pack_amount ("amount",
     71                                         &bank->amount))))
     72         {
     73           GNUNET_break (0);
     74           json_decref (json_history);
     75           return NULL;
     76         }
     77         break;
     78       }
     79     case TALER_EXCHANGEDB_RO_WITHDRAW_COINS:
     80       {
     81         const struct TALER_EXCHANGEDB_Withdraw *withdraw
     82           = pos->details.withdraw;
     83         struct TALER_Amount withdraw_fee;
     84         struct TEH_KeyStateHandle *ksh;
     85 
     86         GNUNET_assert (GNUNET_OK ==
     87                        TALER_amount_set_zero (
     88                          TEH_currency,
     89                          &withdraw_fee));
     90 
     91         /*
     92          * We need to calculate the fees for the withdraw.
     93          * Therefore, we need to get access to the key state.
     94          */
     95         ksh = TEH_keys_get_state ();
     96         if (NULL == ksh)
     97         {
     98           GNUNET_break (0);
     99           json_decref (json_history);
    100           return NULL;
    101         }
    102 
    103         for (size_t i = 0; i < withdraw->num_coins; i++)
    104         {
    105           /* Find the denomination and accumulate the fee */
    106           {
    107             struct TEH_DenominationKey *dk;
    108             dk = TEH_keys_denomination_by_hash_from_state (
    109               ksh,
    110               &withdraw->denom_pub_hashes[i],
    111               NULL,
    112               NULL);
    113 
    114             if (NULL == dk)
    115             {
    116               GNUNET_break (0);
    117               json_decref (json_history);
    118               return NULL;
    119             }
    120 
    121             if (0 > TALER_amount_add (&withdraw_fee,
    122                                       &withdraw_fee,
    123                                       &dk->meta.fees.withdraw))
    124             {
    125               GNUNET_break (0);
    126               json_decref (json_history);
    127               return NULL;
    128             }
    129           }
    130         }
    131 
    132         /* Prepare the entry for the history */
    133         {
    134           json_t *j_entry = GNUNET_JSON_PACK (
    135             GNUNET_JSON_pack_string (
    136               "type",
    137               "WITHDRAW"),
    138             GNUNET_JSON_pack_data_auto (
    139               "reserve_sig",
    140               &withdraw->reserve_sig),
    141             GNUNET_JSON_pack_data_auto (
    142               "planchets_h",
    143               &withdraw->planchets_h),
    144             GNUNET_JSON_pack_uint64 (
    145               "num_coins",
    146               withdraw->num_coins),
    147             TALER_JSON_pack_array_of_data (
    148               "denom_pub_hashes",
    149               withdraw->num_coins,
    150               withdraw->denom_pub_hashes,
    151               sizeof(withdraw->denom_pub_hashes[0])),
    152             TALER_JSON_pack_amount (
    153               "withdraw_fee",
    154               &withdraw_fee),
    155             TALER_JSON_pack_amount (
    156               "amount",
    157               &withdraw->amount_with_fee)
    158             );
    159 
    160           if (! withdraw->no_blinding_seed)
    161           {
    162             if (0!=
    163                 json_object_update_new (
    164                   j_entry,
    165                   GNUNET_JSON_PACK (
    166                     GNUNET_JSON_pack_data_auto (
    167                       "blinding_seed",
    168                       &withdraw->blinding_seed))))
    169             {
    170               GNUNET_break (0);
    171               json_decref (json_history);
    172               return NULL;
    173             }
    174             if (0!=
    175                 json_object_update_new (
    176                   j_entry,
    177                   GNUNET_JSON_PACK (
    178                     TALER_JSON_pack_array_of_data (
    179                       "cs_r_values",
    180                       withdraw->num_cs_r_values,
    181                       withdraw->cs_r_values,
    182                       sizeof(withdraw->cs_r_values[0])))))
    183             {
    184               GNUNET_break (0);
    185               json_decref (json_history);
    186               return NULL;
    187             }
    188           }
    189           if (withdraw->age_proof_required)
    190           {
    191             if (0 !=
    192                 json_object_update_new (
    193                   j_entry,
    194                   GNUNET_JSON_PACK (
    195                     GNUNET_JSON_pack_uint64 (
    196                       "noreveal_index",
    197                       withdraw->noreveal_index),
    198                     GNUNET_JSON_pack_data_auto (
    199                       "selected_h",
    200                       &withdraw->selected_h),
    201                     GNUNET_JSON_pack_uint64 (
    202                       "max_age",
    203                       withdraw->max_age))))
    204             {
    205               GNUNET_break (0);
    206               json_decref (json_history);
    207               return NULL;
    208             }
    209           }
    210 
    211           if (0 !=
    212               json_array_append_new (
    213                 json_history,
    214                 j_entry))
    215           {
    216             GNUNET_break (0);
    217             json_decref (json_history);
    218             return NULL;
    219           }
    220         }
    221       }
    222 
    223       break;
    224     case TALER_EXCHANGEDB_RO_RECOUP_COIN:
    225       {
    226         const struct TALER_EXCHANGEDB_Recoup *recoup
    227           = pos->details.recoup;
    228         struct TALER_ExchangePublicKeyP pub;
    229         struct TALER_ExchangeSignatureP sig;
    230 
    231         if (TALER_EC_NONE !=
    232             TALER_exchange_online_confirm_recoup_sign (
    233               &TEH_keys_exchange_sign_,
    234               recoup->timestamp,
    235               &recoup->value,
    236               &recoup->coin.coin_pub,
    237               &recoup->reserve_pub,
    238               &pub,
    239               &sig))
    240         {
    241           GNUNET_break (0);
    242           json_decref (json_history);
    243           return NULL;
    244         }
    245 
    246         if (0 !=
    247             json_array_append_new (
    248               json_history,
    249               GNUNET_JSON_PACK (
    250                 GNUNET_JSON_pack_string ("type",
    251                                          "RECOUP"),
    252                 GNUNET_JSON_pack_data_auto ("exchange_pub",
    253                                             &pub),
    254                 GNUNET_JSON_pack_data_auto ("exchange_sig",
    255                                             &sig),
    256                 GNUNET_JSON_pack_timestamp ("timestamp",
    257                                             recoup->timestamp),
    258                 TALER_JSON_pack_amount ("amount",
    259                                         &recoup->value),
    260                 GNUNET_JSON_pack_data_auto ("coin_pub",
    261                                             &recoup->coin.coin_pub))))
    262         {
    263           GNUNET_break (0);
    264           json_decref (json_history);
    265           return NULL;
    266         }
    267       }
    268       break;
    269     case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
    270       {
    271         const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
    272           pos->details.closing;
    273         struct TALER_ExchangePublicKeyP pub;
    274         struct TALER_ExchangeSignatureP sig;
    275 
    276         if (TALER_EC_NONE !=
    277             TALER_exchange_online_reserve_closed_sign (
    278               &TEH_keys_exchange_sign_,
    279               closing->execution_date,
    280               &closing->amount,
    281               &closing->closing_fee,
    282               closing->receiver_account_details,
    283               &closing->wtid,
    284               &pos->details.closing->reserve_pub,
    285               &pub,
    286               &sig))
    287         {
    288           GNUNET_break (0);
    289           json_decref (json_history);
    290           return NULL;
    291         }
    292         if (0 !=
    293             json_array_append_new (
    294               json_history,
    295               GNUNET_JSON_PACK (
    296                 GNUNET_JSON_pack_string ("type",
    297                                          "CLOSING"),
    298                 TALER_JSON_pack_full_payto ("receiver_account_details",
    299                                             closing->receiver_account_details),
    300                 GNUNET_JSON_pack_data_auto ("wtid",
    301                                             &closing->wtid),
    302                 GNUNET_JSON_pack_data_auto ("exchange_pub",
    303                                             &pub),
    304                 GNUNET_JSON_pack_data_auto ("exchange_sig",
    305                                             &sig),
    306                 GNUNET_JSON_pack_timestamp ("timestamp",
    307                                             closing->execution_date),
    308                 TALER_JSON_pack_amount ("amount",
    309                                         &closing->amount),
    310                 TALER_JSON_pack_amount ("closing_fee",
    311                                         &closing->closing_fee))))
    312         {
    313           GNUNET_break (0);
    314           json_decref (json_history);
    315           return NULL;
    316         }
    317       }
    318       break;
    319     case TALER_EXCHANGEDB_RO_PURSE_MERGE:
    320       {
    321         const struct TALER_EXCHANGEDB_PurseMerge *merge =
    322           pos->details.merge;
    323 
    324         if (0 !=
    325             json_array_append_new (
    326               json_history,
    327               GNUNET_JSON_PACK (
    328                 GNUNET_JSON_pack_string ("type",
    329                                          "MERGE"),
    330                 GNUNET_JSON_pack_data_auto ("h_contract_terms",
    331                                             &merge->h_contract_terms),
    332                 GNUNET_JSON_pack_data_auto ("merge_pub",
    333                                             &merge->merge_pub),
    334                 GNUNET_JSON_pack_uint64 ("min_age",
    335                                          merge->min_age),
    336                 GNUNET_JSON_pack_uint64 ("flags",
    337                                          merge->flags),
    338                 GNUNET_JSON_pack_data_auto ("purse_pub",
    339                                             &merge->purse_pub),
    340                 GNUNET_JSON_pack_data_auto ("reserve_sig",
    341                                             &merge->reserve_sig),
    342                 GNUNET_JSON_pack_timestamp ("merge_timestamp",
    343                                             merge->merge_timestamp),
    344                 GNUNET_JSON_pack_timestamp ("purse_expiration",
    345                                             merge->purse_expiration),
    346                 TALER_JSON_pack_amount ("purse_fee",
    347                                         &merge->purse_fee),
    348                 TALER_JSON_pack_amount ("amount",
    349                                         &merge->amount_with_fee),
    350                 GNUNET_JSON_pack_bool ("merged",
    351                                        merge->merged))))
    352         {
    353           GNUNET_break (0);
    354           json_decref (json_history);
    355           return NULL;
    356         }
    357       }
    358       break;
    359     case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
    360       {
    361         const struct TALER_EXCHANGEDB_HistoryRequest *history =
    362           pos->details.history;
    363 
    364         if (0 !=
    365             json_array_append_new (
    366               json_history,
    367               GNUNET_JSON_PACK (
    368                 GNUNET_JSON_pack_string ("type",
    369                                          "HISTORY"),
    370                 GNUNET_JSON_pack_data_auto ("reserve_sig",
    371                                             &history->reserve_sig),
    372                 GNUNET_JSON_pack_timestamp ("request_timestamp",
    373                                             history->request_timestamp),
    374                 TALER_JSON_pack_amount ("amount",
    375                                         &history->history_fee))))
    376         {
    377           GNUNET_break (0);
    378           json_decref (json_history);
    379           return NULL;
    380         }
    381       }
    382       break;
    383 
    384     case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
    385       {
    386         const struct TALER_EXCHANGEDB_OpenRequest *orq =
    387           pos->details.open_request;
    388 
    389         if (0 !=
    390             json_array_append_new (
    391               json_history,
    392               GNUNET_JSON_PACK (
    393                 GNUNET_JSON_pack_string ("type",
    394                                          "OPEN"),
    395                 GNUNET_JSON_pack_uint64 ("requested_min_purses",
    396                                          orq->purse_limit),
    397                 GNUNET_JSON_pack_data_auto ("reserve_sig",
    398                                             &orq->reserve_sig),
    399                 GNUNET_JSON_pack_timestamp ("request_timestamp",
    400                                             orq->request_timestamp),
    401                 GNUNET_JSON_pack_timestamp ("requested_expiration",
    402                                             orq->reserve_expiration),
    403                 TALER_JSON_pack_amount ("open_fee",
    404                                         &orq->open_fee))))
    405         {
    406           GNUNET_break (0);
    407           json_decref (json_history);
    408           return NULL;
    409         }
    410       }
    411       break;
    412 
    413     case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
    414       {
    415         const struct TALER_EXCHANGEDB_CloseRequest *crq =
    416           pos->details.close_request;
    417 
    418         if (0 !=
    419             json_array_append_new (
    420               json_history,
    421               GNUNET_JSON_PACK (
    422                 GNUNET_JSON_pack_string ("type",
    423                                          "CLOSE"),
    424                 GNUNET_JSON_pack_data_auto ("reserve_sig",
    425                                             &crq->reserve_sig),
    426                 GNUNET_is_zero (&crq->target_account_h_payto)
    427                 ? GNUNET_JSON_pack_allow_null (
    428                   GNUNET_JSON_pack_string ("h_payto",
    429                                            NULL))
    430                 : GNUNET_JSON_pack_data_auto ("h_payto",
    431                                               &crq->target_account_h_payto),
    432                 GNUNET_JSON_pack_timestamp ("request_timestamp",
    433                                             crq->request_timestamp))))
    434         {
    435           GNUNET_break (0);
    436           json_decref (json_history);
    437           return NULL;
    438         }
    439       }
    440       break;
    441     }
    442   }
    443   return json_history;
    444 }
    445 
    446 
    447 /**
    448  * Add the headers we want to set for every /keys response.
    449  *
    450  * @param cls the key state to use
    451  * @param[in,out] response the response to modify
    452  */
    453 static void
    454 add_response_headers (void *cls,
    455                       struct MHD_Response *response)
    456 {
    457   (void) cls;
    458   GNUNET_break (MHD_YES ==
    459                 MHD_add_response_header (response,
    460                                          MHD_HTTP_HEADER_CACHE_CONTROL,
    461                                          "no-cache"));
    462 }
    463 
    464 
    465 MHD_RESULT
    466 TEH_handler_reserves_history (
    467   struct TEH_RequestContext *rc,
    468   const struct TALER_ReservePublicKeyP *reserve_pub)
    469 {
    470   struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
    471   uint64_t start_off = 0;
    472   struct TALER_Amount balance;
    473   uint64_t etag_in;
    474   uint64_t etag_out;
    475   char etagp[24];
    476   struct MHD_Response *resp;
    477   unsigned int http_status;
    478 
    479   TALER_MHD_parse_request_number (rc->connection,
    480                                   "start",
    481                                   &start_off);
    482   {
    483     struct TALER_ReserveSignatureP reserve_sig;
    484     bool required = true;
    485 
    486     TALER_MHD_parse_request_header_auto (rc->connection,
    487                                          TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
    488                                          &reserve_sig,
    489                                          required);
    490 
    491     if (GNUNET_OK !=
    492         TALER_wallet_reserve_history_verify (start_off,
    493                                              reserve_pub,
    494                                              &reserve_sig))
    495     {
    496       GNUNET_break_op (0);
    497       return TALER_MHD_reply_with_error (rc->connection,
    498                                          MHD_HTTP_FORBIDDEN,
    499                                          TALER_EC_EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE,
    500                                          NULL);
    501     }
    502   }
    503 
    504   /* Get etag */
    505   {
    506     const char *etags;
    507 
    508     etags = MHD_lookup_connection_value (rc->connection,
    509                                          MHD_HEADER_KIND,
    510                                          MHD_HTTP_HEADER_IF_NONE_MATCH);
    511     if (NULL != etags)
    512     {
    513       char dummy;
    514       unsigned long long ev;
    515 
    516       if (1 != sscanf (etags,
    517                        "\"%llu\"%c",
    518                        &ev,
    519                        &dummy))
    520       {
    521         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    522                     "Client send malformed `If-None-Match' header `%s'\n",
    523                     etags);
    524         etag_in = 0;
    525       }
    526       else
    527       {
    528         etag_in = (uint64_t) ev;
    529       }
    530     }
    531     else
    532     {
    533       etag_in = start_off;
    534     }
    535   }
    536 
    537   {
    538     enum GNUNET_DB_QueryStatus qs;
    539 
    540     qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
    541                                           reserve_pub,
    542                                           start_off,
    543                                           etag_in,
    544                                           &etag_out,
    545                                           &balance,
    546                                           &rh);
    547     switch (qs)
    548     {
    549     case GNUNET_DB_STATUS_HARD_ERROR:
    550       GNUNET_break (0);
    551       return TALER_MHD_reply_with_error (rc->connection,
    552                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    553                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    554                                          "get_reserve_history");
    555     case GNUNET_DB_STATUS_SOFT_ERROR:
    556       return TALER_MHD_reply_with_error (rc->connection,
    557                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    558                                          TALER_EC_GENERIC_DB_SOFT_FAILURE,
    559                                          "get_reserve_history");
    560     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    561       return TALER_MHD_reply_with_error (rc->connection,
    562                                          MHD_HTTP_NOT_FOUND,
    563                                          TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
    564                                          NULL);
    565     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    566       /* Handled below */
    567       break;
    568     }
    569   }
    570 
    571   GNUNET_snprintf (etagp,
    572                    sizeof (etagp),
    573                    "\"%llu\"",
    574                    (unsigned long long) etag_out);
    575   if (etag_in == etag_out)
    576   {
    577     TEH_plugin->free_reserve_history (TEH_plugin->cls,
    578                                       rh);
    579     return TEH_RESPONSE_reply_not_modified (rc->connection,
    580                                             etagp,
    581                                             &add_response_headers,
    582                                             NULL);
    583   }
    584   if (NULL == rh)
    585   {
    586     /* 204: empty history */
    587     resp = MHD_create_response_from_buffer_static (0,
    588                                                    "");
    589     http_status = MHD_HTTP_NO_CONTENT;
    590   }
    591   else
    592   {
    593     json_t *history;
    594 
    595     http_status = MHD_HTTP_OK;
    596     history = compile_reserve_history (rh);
    597     TEH_plugin->free_reserve_history (TEH_plugin->cls,
    598                                       rh);
    599     rh = NULL;
    600     if (NULL == history)
    601     {
    602       GNUNET_break (0);
    603       return TALER_MHD_reply_with_error (rc->connection,
    604                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    605                                          TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
    606                                          NULL);
    607     }
    608     resp = TALER_MHD_MAKE_JSON_PACK (
    609       TALER_JSON_pack_amount ("balance",
    610                               &balance),
    611       GNUNET_JSON_pack_array_steal ("history",
    612                                     history));
    613   }
    614   add_response_headers (NULL,
    615                         resp);
    616   GNUNET_break (MHD_YES ==
    617                 MHD_add_response_header (resp,
    618                                          MHD_HTTP_HEADER_ETAG,
    619                                          etagp));
    620   {
    621     MHD_RESULT ret;
    622 
    623     ret = MHD_queue_response (rc->connection,
    624                               http_status,
    625                               resp);
    626     GNUNET_break (MHD_YES == ret);
    627     MHD_destroy_response (resp);
    628     return ret;
    629   }
    630 }
    631 
    632 
    633 /* end of taler-exchange-httpd_reserves_history.c */