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_coins_get.c (28522B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2023 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_coins_get.c
     18  * @brief Handle GET /coins/$COIN_PUB/history requests
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include <gnunet/gnunet_util_lib.h>
     23 #include <jansson.h>
     24 #include "taler/taler_mhd_lib.h"
     25 #include "taler/taler_json_lib.h"
     26 #include "taler/taler_dbevents.h"
     27 #include "taler-exchange-httpd_keys.h"
     28 #include "taler-exchange-httpd_coins_get.h"
     29 #include "taler-exchange-httpd_responses.h"
     30 
     31 
     32 /**
     33  * Add the headers we want to set for every response.
     34  *
     35  * @param cls the key state to use
     36  * @param[in,out] response the response to modify
     37  */
     38 static void
     39 add_response_headers (void *cls,
     40                       struct MHD_Response *response)
     41 {
     42   (void) cls;
     43   GNUNET_break (MHD_YES ==
     44                 MHD_add_response_header (response,
     45                                          MHD_HTTP_HEADER_CACHE_CONTROL,
     46                                          "no-cache"));
     47 }
     48 
     49 
     50 /**
     51  * Compile the transaction history of a coin into a JSON object.
     52  *
     53  * @param coin_pub public key of the coin
     54  * @param tl transaction history to JSON-ify
     55  * @return json representation of the @a rh, NULL on error
     56  */
     57 static json_t *
     58 compile_transaction_history (
     59   const struct TALER_CoinSpendPublicKeyP *coin_pub,
     60   const struct TALER_EXCHANGEDB_TransactionList *tl)
     61 {
     62   json_t *history;
     63 
     64   history = json_array ();
     65   if (NULL == history)
     66   {
     67     GNUNET_break (0); /* out of memory!? */
     68     return NULL;
     69   }
     70   for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
     71        NULL != pos;
     72        pos = pos->next)
     73   {
     74     switch (pos->type)
     75     {
     76     case TALER_EXCHANGEDB_TT_DEPOSIT:
     77       {
     78         const struct TALER_EXCHANGEDB_DepositListEntry *deposit =
     79           pos->details.deposit;
     80         struct TALER_MerchantWireHashP h_wire;
     81 
     82         TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
     83                                             &deposit->wire_salt,
     84                                             &h_wire);
     85 #if ENABLE_SANITY_CHECKS
     86         /* internal sanity check before we hand out a bogus sig... */
     87         TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
     88         if (GNUNET_OK !=
     89             TALER_wallet_deposit_verify (
     90               &deposit->amount_with_fee,
     91               &deposit->deposit_fee,
     92               &h_wire,
     93               &deposit->h_contract_terms,
     94               deposit->no_wallet_data_hash
     95               ? NULL
     96               : &deposit->wallet_data_hash,
     97               deposit->no_age_commitment
     98               ? NULL
     99               : &deposit->h_age_commitment,
    100               &deposit->h_policy,
    101               &deposit->h_denom_pub,
    102               deposit->timestamp,
    103               &deposit->merchant_pub,
    104               deposit->refund_deadline,
    105               coin_pub,
    106               &deposit->csig))
    107         {
    108           GNUNET_break (0);
    109           json_decref (history);
    110           return NULL;
    111         }
    112 #endif
    113         if (0 !=
    114             json_array_append_new (
    115               history,
    116               GNUNET_JSON_PACK (
    117                 GNUNET_JSON_pack_string ("type",
    118                                          "DEPOSIT"),
    119                 GNUNET_JSON_pack_uint64 ("history_offset",
    120                                          pos->coin_history_id),
    121                 TALER_JSON_pack_amount ("amount",
    122                                         &deposit->amount_with_fee),
    123                 TALER_JSON_pack_amount ("deposit_fee",
    124                                         &deposit->deposit_fee),
    125                 GNUNET_JSON_pack_data_auto ("merchant_pub",
    126                                             &deposit->merchant_pub),
    127                 GNUNET_JSON_pack_timestamp ("timestamp",
    128                                             deposit->timestamp),
    129                 GNUNET_JSON_pack_allow_null (
    130                   GNUNET_JSON_pack_timestamp ("refund_deadline",
    131                                               deposit->refund_deadline)),
    132                 GNUNET_JSON_pack_data_auto ("h_contract_terms",
    133                                             &deposit->h_contract_terms),
    134                 GNUNET_JSON_pack_data_auto ("h_wire",
    135                                             &h_wire),
    136                 GNUNET_JSON_pack_data_auto ("h_denom_pub",
    137                                             &deposit->h_denom_pub),
    138                 GNUNET_JSON_pack_allow_null (
    139                   deposit->has_policy
    140                   ? GNUNET_JSON_pack_data_auto ("h_policy",
    141                                                 &deposit->h_policy)
    142                   : GNUNET_JSON_pack_string (
    143                     "h_policy",
    144                     NULL)),
    145                 GNUNET_JSON_pack_allow_null (
    146                   deposit->no_wallet_data_hash
    147                   ? GNUNET_JSON_pack_string (
    148                     "wallet_data_hash",
    149                     NULL)
    150                   : GNUNET_JSON_pack_data_auto ("wallet_data_hash",
    151                                                 &deposit->wallet_data_hash)),
    152                 GNUNET_JSON_pack_allow_null (
    153                   deposit->no_age_commitment
    154                   ? GNUNET_JSON_pack_string (
    155                     "h_age_commitment",
    156                     NULL)
    157                   : GNUNET_JSON_pack_data_auto ("h_age_commitment",
    158                                                 &deposit->h_age_commitment)),
    159                 GNUNET_JSON_pack_data_auto ("coin_sig",
    160                                             &deposit->csig))))
    161         {
    162           GNUNET_break (0);
    163           json_decref (history);
    164           return NULL;
    165         }
    166         break;
    167       }
    168     case TALER_EXCHANGEDB_TT_MELT:
    169       {
    170         const struct TALER_EXCHANGEDB_MeltListEntry *melt =
    171           pos->details.melt;
    172         const struct TALER_AgeCommitmentHashP *phac;
    173         const struct TALER_BlindingMasterSeedP *pbs;
    174 
    175 #if ENABLE_SANITY_CHECKS
    176         TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    177         if (GNUNET_OK !=
    178             TALER_wallet_melt_verify (
    179               &melt->amount_with_fee,
    180               &melt->melt_fee,
    181               &melt->rc,
    182               &melt->h_denom_pub,
    183               &melt->h_age_commitment,
    184               coin_pub,
    185               &melt->coin_sig))
    186         {
    187           GNUNET_break (0);
    188           json_decref (history);
    189           return NULL;
    190         }
    191 #endif
    192         phac = (melt->no_age_commitment)
    193                 ? NULL
    194                 :  &melt->h_age_commitment;
    195         pbs = melt->no_blinding_seed
    196                 ? NULL
    197                 : &melt->blinding_seed;
    198         if (0 !=
    199             json_array_append_new (
    200               history,
    201               GNUNET_JSON_PACK (
    202                 GNUNET_JSON_pack_string ("type",
    203                                          "MELT"),
    204                 GNUNET_JSON_pack_uint64 ("history_offset",
    205                                          pos->coin_history_id),
    206                 TALER_JSON_pack_amount ("amount",
    207                                         &melt->amount_with_fee),
    208                 TALER_JSON_pack_amount ("melt_fee",
    209                                         &melt->melt_fee),
    210                 GNUNET_JSON_pack_data_auto ("rc",
    211                                             &melt->rc),
    212                 GNUNET_JSON_pack_data_auto ("h_denom_pub",
    213                                             &melt->h_denom_pub),
    214                 GNUNET_JSON_pack_data_auto ("refresh_seed",
    215                                             &melt->refresh_seed),
    216                 GNUNET_JSON_pack_allow_null (
    217                   GNUNET_JSON_pack_data_auto ("blinding_seed",
    218                                               pbs)),
    219                 GNUNET_JSON_pack_allow_null (
    220                   GNUNET_JSON_pack_data_auto ("h_age_commitment",
    221                                               phac)),
    222                 GNUNET_JSON_pack_data_auto ("coin_sig",
    223                                             &melt->coin_sig))))
    224         {
    225           GNUNET_break (0);
    226           json_decref (history);
    227           return NULL;
    228         }
    229       }
    230       break;
    231     case TALER_EXCHANGEDB_TT_REFUND:
    232       {
    233         const struct TALER_EXCHANGEDB_RefundListEntry *refund =
    234           pos->details.refund;
    235         struct TALER_Amount value;
    236 
    237 #if ENABLE_SANITY_CHECKS
    238         TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    239         if (GNUNET_OK !=
    240             TALER_merchant_refund_verify (
    241               coin_pub,
    242               &refund->h_contract_terms,
    243               refund->rtransaction_id,
    244               &refund->refund_amount,
    245               &refund->merchant_pub,
    246               &refund->merchant_sig))
    247         {
    248           GNUNET_break (0);
    249           json_decref (history);
    250           return NULL;
    251         }
    252 #endif
    253         if (0 >
    254             TALER_amount_subtract (&value,
    255                                    &refund->refund_amount,
    256                                    &refund->refund_fee))
    257         {
    258           GNUNET_break (0);
    259           json_decref (history);
    260           return NULL;
    261         }
    262         if (0 !=
    263             json_array_append_new (
    264               history,
    265               GNUNET_JSON_PACK (
    266                 GNUNET_JSON_pack_string ("type",
    267                                          "REFUND"),
    268                 GNUNET_JSON_pack_uint64 ("history_offset",
    269                                          pos->coin_history_id),
    270                 TALER_JSON_pack_amount ("amount",
    271                                         &value),
    272                 TALER_JSON_pack_amount ("refund_fee",
    273                                         &refund->refund_fee),
    274                 GNUNET_JSON_pack_data_auto ("h_contract_terms",
    275                                             &refund->h_contract_terms),
    276                 GNUNET_JSON_pack_data_auto ("merchant_pub",
    277                                             &refund->merchant_pub),
    278                 GNUNET_JSON_pack_uint64 ("rtransaction_id",
    279                                          refund->rtransaction_id),
    280                 GNUNET_JSON_pack_data_auto ("merchant_sig",
    281                                             &refund->merchant_sig))))
    282         {
    283           GNUNET_break (0);
    284           json_decref (history);
    285           return NULL;
    286         }
    287       }
    288       break;
    289     case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
    290       {
    291         struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
    292           pos->details.old_coin_recoup;
    293         struct TALER_ExchangePublicKeyP epub;
    294         struct TALER_ExchangeSignatureP esig;
    295 
    296         if (TALER_EC_NONE !=
    297             TALER_exchange_online_confirm_recoup_refresh_sign (
    298               &TEH_keys_exchange_sign_,
    299               pr->timestamp,
    300               &pr->value,
    301               &pr->coin.coin_pub,
    302               &pr->old_coin_pub,
    303               &epub,
    304               &esig))
    305         {
    306           GNUNET_break (0);
    307           json_decref (history);
    308           return NULL;
    309         }
    310         /* NOTE: we could also provide coin_pub's coin_sig, denomination key hash and
    311            the denomination key's RSA signature over coin_pub, but as the
    312            wallet should really already have this information (and cannot
    313            check or do anything with it anyway if it doesn't), it seems
    314            strictly unnecessary. */
    315         if (0 !=
    316             json_array_append_new (
    317               history,
    318               GNUNET_JSON_PACK (
    319                 GNUNET_JSON_pack_string ("type",
    320                                          "RECOUP-REFRESH-RECEIVER"),
    321                 GNUNET_JSON_pack_uint64 ("history_offset",
    322                                          pos->coin_history_id),
    323                 TALER_JSON_pack_amount ("amount",
    324                                         &pr->value),
    325                 GNUNET_JSON_pack_data_auto ("exchange_sig",
    326                                             &esig),
    327                 GNUNET_JSON_pack_data_auto ("exchange_pub",
    328                                             &epub),
    329                 GNUNET_JSON_pack_data_auto ("coin_pub",
    330                                             &pr->coin.coin_pub),
    331                 GNUNET_JSON_pack_timestamp ("timestamp",
    332                                             pr->timestamp))))
    333         {
    334           GNUNET_break (0);
    335           json_decref (history);
    336           return NULL;
    337         }
    338         break;
    339       }
    340     case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
    341       {
    342         const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
    343           pos->details.recoup;
    344         struct TALER_ExchangePublicKeyP epub;
    345         struct TALER_ExchangeSignatureP esig;
    346 
    347         if (TALER_EC_NONE !=
    348             TALER_exchange_online_confirm_recoup_sign (
    349               &TEH_keys_exchange_sign_,
    350               recoup->timestamp,
    351               &recoup->value,
    352               coin_pub,
    353               &recoup->reserve_pub,
    354               &epub,
    355               &esig))
    356         {
    357           GNUNET_break (0);
    358           json_decref (history);
    359           return NULL;
    360         }
    361         if (0 !=
    362             json_array_append_new (
    363               history,
    364               GNUNET_JSON_PACK (
    365                 GNUNET_JSON_pack_string ("type",
    366                                          "RECOUP-WITHDRAW"),
    367                 GNUNET_JSON_pack_uint64 ("history_offset",
    368                                          pos->coin_history_id),
    369                 TALER_JSON_pack_amount ("amount",
    370                                         &recoup->value),
    371                 GNUNET_JSON_pack_data_auto ("exchange_sig",
    372                                             &esig),
    373                 GNUNET_JSON_pack_data_auto ("exchange_pub",
    374                                             &epub),
    375                 GNUNET_JSON_pack_data_auto ("reserve_pub",
    376                                             &recoup->reserve_pub),
    377                 GNUNET_JSON_pack_data_auto ("coin_sig",
    378                                             &recoup->coin_sig),
    379                 GNUNET_JSON_pack_data_auto ("h_denom_pub",
    380                                             &recoup->h_denom_pub),
    381                 GNUNET_JSON_pack_data_auto ("coin_blind",
    382                                             &recoup->coin_blind),
    383                 // FIXME-9828: spec says we should have h_commitment?
    384                 // FIXME-9828: spec says we should have coin_index?
    385                 GNUNET_JSON_pack_data_auto ("reserve_pub",
    386                                             &recoup->reserve_pub),
    387                 GNUNET_JSON_pack_timestamp ("timestamp",
    388                                             recoup->timestamp))))
    389         {
    390           GNUNET_break (0);
    391           json_decref (history);
    392           return NULL;
    393         }
    394       }
    395       break;
    396     case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
    397       {
    398         struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
    399           pos->details.recoup_refresh;
    400         struct TALER_ExchangePublicKeyP epub;
    401         struct TALER_ExchangeSignatureP esig;
    402 
    403         if (TALER_EC_NONE !=
    404             TALER_exchange_online_confirm_recoup_refresh_sign (
    405               &TEH_keys_exchange_sign_,
    406               pr->timestamp,
    407               &pr->value,
    408               coin_pub,
    409               &pr->old_coin_pub,
    410               &epub,
    411               &esig))
    412         {
    413           GNUNET_break (0);
    414           json_decref (history);
    415           return NULL;
    416         }
    417         /* NOTE: we could also provide coin_pub's coin_sig, denomination key
    418            hash and the denomination key's RSA signature over coin_pub, but as
    419            the wallet should really already have this information (and cannot
    420            check or do anything with it anyway if it doesn't), it seems
    421            strictly unnecessary. */
    422         if (0 !=
    423             json_array_append_new (
    424               history,
    425               GNUNET_JSON_PACK (
    426                 GNUNET_JSON_pack_string ("type",
    427                                          "RECOUP-REFRESH"),
    428                 GNUNET_JSON_pack_uint64 ("history_offset",
    429                                          pos->coin_history_id),
    430                 TALER_JSON_pack_amount ("amount",
    431                                         &pr->value),
    432                 GNUNET_JSON_pack_data_auto ("exchange_sig",
    433                                             &esig),
    434                 GNUNET_JSON_pack_data_auto ("exchange_pub",
    435                                             &epub),
    436                 GNUNET_JSON_pack_data_auto ("old_coin_pub",
    437                                             &pr->old_coin_pub),
    438                 GNUNET_JSON_pack_data_auto ("coin_sig",
    439                                             &pr->coin_sig),
    440                 // FIXME-#9828: spec says to return h_denom_pub
    441                 GNUNET_JSON_pack_data_auto ("coin_blind",
    442                                             &pr->coin_blind),
    443                 // FIXME-#9828: spec says to return h_commitment
    444                 // FIXME-#9828: spec says to return coin_index
    445                 // FIXME-#9828: spec says to return new_coin_blinding_secret
    446                 // FIXME-#9828: spec says to return new_coin_ev
    447                 GNUNET_JSON_pack_timestamp ("timestamp",
    448                                             pr->timestamp))))
    449         {
    450           GNUNET_break (0);
    451           json_decref (history);
    452           return NULL;
    453         }
    454         break;
    455       }
    456 
    457     case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
    458       {
    459         struct TALER_EXCHANGEDB_PurseDepositListEntry *pd
    460           = pos->details.purse_deposit;
    461         const struct TALER_AgeCommitmentHashP *phac = NULL;
    462 
    463         if (! pd->no_age_commitment)
    464           phac = &pd->h_age_commitment;
    465         if (0 !=
    466             json_array_append_new (
    467               history,
    468               GNUNET_JSON_PACK (
    469                 GNUNET_JSON_pack_string ("type",
    470                                          "PURSE-DEPOSIT"),
    471                 GNUNET_JSON_pack_uint64 ("history_offset",
    472                                          pos->coin_history_id),
    473                 TALER_JSON_pack_amount ("amount",
    474                                         &pd->amount),
    475                 GNUNET_JSON_pack_string ("exchange_base_url",
    476                                          NULL == pd->exchange_base_url
    477                                          ? TEH_base_url
    478                                          : pd->exchange_base_url),
    479                 GNUNET_JSON_pack_allow_null (
    480                   GNUNET_JSON_pack_data_auto ("h_age_commitment",
    481                                               phac)),
    482                 TALER_JSON_pack_amount ("deposit_fee",
    483                                         &pd->deposit_fee),
    484                 GNUNET_JSON_pack_data_auto ("purse_pub",
    485                                             &pd->purse_pub),
    486                 GNUNET_JSON_pack_bool ("refunded",
    487                                        pd->refunded),
    488                 GNUNET_JSON_pack_data_auto ("coin_sig",
    489                                             &pd->coin_sig),
    490                 GNUNET_JSON_pack_data_auto ("h_denom_pub",
    491                                             &pd->h_denom_pub))))
    492         {
    493           GNUNET_break (0);
    494           json_decref (history);
    495           return NULL;
    496         }
    497         break;
    498       }
    499 
    500     case TALER_EXCHANGEDB_TT_PURSE_REFUND:
    501       {
    502         const struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund =
    503           pos->details.purse_refund;
    504         struct TALER_Amount value;
    505         enum TALER_ErrorCode ec;
    506         struct TALER_ExchangePublicKeyP epub;
    507         struct TALER_ExchangeSignatureP esig;
    508 
    509         if (0 >
    510             TALER_amount_subtract (&value,
    511                                    &prefund->refund_amount,
    512                                    &prefund->refund_fee))
    513         {
    514           GNUNET_break (0);
    515           json_decref (history);
    516           return NULL;
    517         }
    518         ec = TALER_exchange_online_purse_refund_sign (
    519           &TEH_keys_exchange_sign_,
    520           &value,
    521           &prefund->refund_fee,
    522           coin_pub,
    523           &prefund->purse_pub,
    524           &epub,
    525           &esig);
    526         if (TALER_EC_NONE != ec)
    527         {
    528           GNUNET_break (0);
    529           json_decref (history);
    530           return NULL;
    531         }
    532         if (0 !=
    533             json_array_append_new (
    534               history,
    535               GNUNET_JSON_PACK (
    536                 GNUNET_JSON_pack_string ("type",
    537                                          "PURSE-REFUND"),
    538                 GNUNET_JSON_pack_uint64 ("history_offset",
    539                                          pos->coin_history_id),
    540                 TALER_JSON_pack_amount ("amount",
    541                                         &value),
    542                 TALER_JSON_pack_amount ("refund_fee",
    543                                         &prefund->refund_fee),
    544                 GNUNET_JSON_pack_data_auto ("exchange_sig",
    545                                             &esig),
    546                 GNUNET_JSON_pack_data_auto ("exchange_pub",
    547                                             &epub),
    548                 GNUNET_JSON_pack_data_auto ("purse_pub",
    549                                             &prefund->purse_pub))))
    550         {
    551           GNUNET_break (0);
    552           json_decref (history);
    553           return NULL;
    554         }
    555       }
    556       break;
    557 
    558     case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
    559       {
    560         struct TALER_EXCHANGEDB_ReserveOpenListEntry *role
    561           = pos->details.reserve_open;
    562 
    563         if (0 !=
    564             json_array_append_new (
    565               history,
    566               GNUNET_JSON_PACK (
    567                 GNUNET_JSON_pack_string ("type",
    568                                          "RESERVE-OPEN-DEPOSIT"),
    569                 GNUNET_JSON_pack_uint64 ("history_offset",
    570                                          pos->coin_history_id),
    571                 TALER_JSON_pack_amount ("coin_contribution",
    572                                         &role->coin_contribution),
    573                 GNUNET_JSON_pack_data_auto ("reserve_sig",
    574                                             &role->reserve_sig),
    575                 GNUNET_JSON_pack_data_auto ("coin_sig",
    576                                             &role->coin_sig))))
    577         {
    578           GNUNET_break (0);
    579           json_decref (history);
    580           return NULL;
    581         }
    582         break;
    583       }
    584     }
    585   }
    586   return history;
    587 }
    588 
    589 
    590 MHD_RESULT
    591 TEH_handler_coins_get (struct TEH_RequestContext *rc,
    592                        const struct TALER_CoinSpendPublicKeyP *coin_pub)
    593 {
    594   struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
    595   uint64_t start_off = 0;
    596   uint64_t etag_in;
    597   uint64_t etag_out;
    598   char etagp[24];
    599   struct MHD_Response *resp;
    600   unsigned int http_status;
    601   struct TALER_DenominationHashP h_denom_pub;
    602   struct TALER_Amount balance;
    603 
    604   TALER_MHD_parse_request_number (rc->connection,
    605                                   "start",
    606                                   &start_off);
    607   /* Check signature */
    608   {
    609     struct TALER_CoinSpendSignatureP coin_sig;
    610     bool required = true;
    611 
    612     TALER_MHD_parse_request_header_auto (rc->connection,
    613                                          TALER_COIN_HISTORY_SIGNATURE_HEADER,
    614                                          &coin_sig,
    615                                          required);
    616     if (GNUNET_OK !=
    617         TALER_wallet_coin_history_verify (start_off,
    618                                           coin_pub,
    619                                           &coin_sig))
    620     {
    621       GNUNET_break_op (0);
    622       return TALER_MHD_reply_with_error (rc->connection,
    623                                          MHD_HTTP_FORBIDDEN,
    624                                          TALER_EC_EXCHANGE_COIN_HISTORY_BAD_SIGNATURE,
    625                                          NULL);
    626     }
    627   }
    628 
    629   /* Get etag */
    630   {
    631     const char *etags;
    632 
    633     etags = MHD_lookup_connection_value (rc->connection,
    634                                          MHD_HEADER_KIND,
    635                                          MHD_HTTP_HEADER_IF_NONE_MATCH);
    636     if (NULL != etags)
    637     {
    638       char dummy;
    639       unsigned long long ev;
    640 
    641       if (1 != sscanf (etags,
    642                        "\"%llu\"%c",
    643                        &ev,
    644                        &dummy))
    645       {
    646         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    647                     "Client send malformed `If-None-Match' header `%s'\n",
    648                     etags);
    649         etag_in = start_off;
    650       }
    651       else
    652       {
    653         etag_in = (uint64_t) ev;
    654       }
    655     }
    656     else
    657     {
    658       etag_in = start_off;
    659     }
    660   }
    661 
    662   /* Get history from DB between etag and now */
    663   {
    664     enum GNUNET_DB_QueryStatus qs;
    665 
    666     qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
    667                                             true,
    668                                             coin_pub,
    669                                             start_off,
    670                                             etag_in,
    671                                             &etag_out,
    672                                             &balance,
    673                                             &h_denom_pub,
    674                                             &tl);
    675     switch (qs)
    676     {
    677     case GNUNET_DB_STATUS_HARD_ERROR:
    678       GNUNET_break (0);
    679       return TALER_MHD_reply_with_error (rc->connection,
    680                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    681                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    682                                          "get_coin_history");
    683     case GNUNET_DB_STATUS_SOFT_ERROR:
    684       return TALER_MHD_reply_with_error (rc->connection,
    685                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    686                                          TALER_EC_GENERIC_DB_SOFT_FAILURE,
    687                                          "get_coin_history");
    688     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    689       return TALER_MHD_reply_with_error (rc->connection,
    690                                          MHD_HTTP_NOT_FOUND,
    691                                          TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
    692                                          NULL);
    693     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    694       /* Handled below */
    695       break;
    696     }
    697   }
    698 
    699   GNUNET_snprintf (etagp,
    700                    sizeof (etagp),
    701                    "\"%llu\"",
    702                    (unsigned long long) etag_out);
    703   if (etag_in == etag_out)
    704   {
    705     return TEH_RESPONSE_reply_not_modified (rc->connection,
    706                                             etagp,
    707                                             &add_response_headers,
    708                                             NULL);
    709   }
    710   if (NULL == tl)
    711   {
    712     /* 204: empty history */
    713     resp = MHD_create_response_from_buffer_static (0,
    714                                                    "");
    715     http_status = MHD_HTTP_NO_CONTENT;
    716   }
    717   else
    718   {
    719     /* 200: regular history */
    720     json_t *history;
    721 
    722     history = compile_transaction_history (coin_pub,
    723                                            tl);
    724     TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
    725                                             tl);
    726     tl = NULL;
    727     if (NULL == history)
    728     {
    729       GNUNET_break (0);
    730       return TALER_MHD_reply_with_error (rc->connection,
    731                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    732                                          TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
    733                                          "Failed to compile coin history");
    734     }
    735     resp = TALER_MHD_MAKE_JSON_PACK (
    736       GNUNET_JSON_pack_data_auto ("h_denom_pub",
    737                                   &h_denom_pub),
    738       TALER_JSON_pack_amount ("balance",
    739                               &balance),
    740       GNUNET_JSON_pack_array_steal ("history",
    741                                     history));
    742     http_status = MHD_HTTP_OK;
    743   }
    744   add_response_headers (NULL,
    745                         resp);
    746   GNUNET_break (MHD_YES ==
    747                 MHD_add_response_header (resp,
    748                                          MHD_HTTP_HEADER_ETAG,
    749                                          etagp));
    750   {
    751     MHD_RESULT ret;
    752 
    753     ret = MHD_queue_response (rc->connection,
    754                               http_status,
    755                               resp);
    756     GNUNET_break (MHD_YES == ret);
    757     MHD_destroy_response (resp);
    758     return ret;
    759   }
    760 }
    761 
    762 
    763 /* end of taler-exchange-httpd_coins_get.c */