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_get-reserves-RESERVE_PUB-history.c (22480B)


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