exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_get-reserves-RESERVE_PUB-history.c (39077B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU 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 General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file lib/exchange_api_get-reserves-RESERVE_PUB-history.c
     19  * @brief Implementation of the GET /reserves/$RESERVE_PUB/history requests
     20  * @author Christian Grothoff
     21  */
     22 #include <jansson.h>
     23 #include <microhttpd.h> /* just for HTTP history codes */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_json_lib.h"
     28 #include "exchange_api_handle.h"
     29 #include "taler/taler_signatures.h"
     30 #include "exchange_api_curl_defaults.h"
     31 
     32 
     33 /**
     34  * @brief A GET /reserves/$RESERVE_PUB/history Handle
     35  */
     36 struct TALER_EXCHANGE_GetReservesHistoryHandle
     37 {
     38 
     39   /**
     40    * Base URL of the exchange.
     41    */
     42   char *base_url;
     43 
     44   /**
     45    * The url for this request.
     46    */
     47   char *url;
     48 
     49   /**
     50    * The keys of the exchange this request handle will use.
     51    */
     52   struct TALER_EXCHANGE_Keys *keys;
     53 
     54   /**
     55    * Handle for the request.
     56    */
     57   struct GNUNET_CURL_Job *job;
     58 
     59   /**
     60    * Function to call with the result.
     61    */
     62   TALER_EXCHANGE_GetReservesHistoryCallback cb;
     63 
     64   /**
     65    * Closure for @e cb.
     66    */
     67   TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls;
     68 
     69   /**
     70    * CURL context to use.
     71    */
     72   struct GNUNET_CURL_Context *ctx;
     73 
     74   /**
     75    * Private key of the reserve we are querying.
     76    */
     77   struct TALER_ReservePrivateKeyP reserve_priv;
     78 
     79   /**
     80    * Public key of the reserve we are querying.
     81    */
     82   struct TALER_ReservePublicKeyP reserve_pub;
     83 
     84   /**
     85    * Where to store the etag (if any).
     86    */
     87   uint64_t etag;
     88 
     89   /**
     90    * Options for the request.
     91    */
     92   struct
     93   {
     94     /**
     95      * Only return entries with offset strictly greater than this value.
     96      */
     97     uint64_t start_off;
     98   } options;
     99 
    100 };
    101 
    102 
    103 /**
    104  * Context for history entry helpers.
    105  */
    106 struct HistoryParseContext
    107 {
    108 
    109   /**
    110    * Keys of the exchange we use.
    111    */
    112   const struct TALER_EXCHANGE_Keys *keys;
    113 
    114   /**
    115    * Our reserve public key.
    116    */
    117   const struct TALER_ReservePublicKeyP *reserve_pub;
    118 
    119   /**
    120    * Array of UUIDs.
    121    */
    122   struct GNUNET_HashCode *uuids;
    123 
    124   /**
    125    * Where to sum up total inbound amounts.
    126    */
    127   struct TALER_Amount *total_in;
    128 
    129   /**
    130    * Where to sum up total outbound amounts.
    131    */
    132   struct TALER_Amount *total_out;
    133 
    134   /**
    135    * Number of entries already used in @e uuids.
    136    */
    137   unsigned int uuid_off;
    138 };
    139 
    140 
    141 /**
    142  * Type of a function called to parse a reserve history
    143  * entry @a rh.
    144  *
    145  * @param[in,out] rh where to write the result
    146  * @param[in,out] uc UUID context for duplicate detection
    147  * @param transaction the transaction to parse
    148  * @return #GNUNET_OK on success
    149  */
    150 typedef enum GNUNET_GenericReturnValue
    151 (*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    152                struct HistoryParseContext *uc,
    153                const json_t *transaction);
    154 
    155 
    156 /**
    157  * Parse "credit" reserve history entry.
    158  *
    159  * @param[in,out] rh entry to parse
    160  * @param uc our context
    161  * @param transaction the transaction to parse
    162  * @return #GNUNET_OK on success
    163  */
    164 static enum GNUNET_GenericReturnValue
    165 parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    166               struct HistoryParseContext *uc,
    167               const json_t *transaction)
    168 {
    169   struct TALER_FullPayto wire_uri;
    170   uint64_t wire_reference;
    171   struct GNUNET_TIME_Timestamp timestamp;
    172   struct GNUNET_JSON_Specification withdraw_spec[] = {
    173     GNUNET_JSON_spec_uint64 ("wire_reference",
    174                              &wire_reference),
    175     GNUNET_JSON_spec_timestamp ("timestamp",
    176                                 &timestamp),
    177     TALER_JSON_spec_full_payto_uri ("sender_account_url",
    178                                     &wire_uri),
    179     GNUNET_JSON_spec_end ()
    180   };
    181 
    182   rh->type = TALER_EXCHANGE_RTT_CREDIT;
    183   if (0 >
    184       TALER_amount_add (uc->total_in,
    185                         uc->total_in,
    186                         &rh->amount))
    187   {
    188     /* overflow in history already!? inconceivable! Bad exchange! */
    189     GNUNET_break_op (0);
    190     return GNUNET_SYSERR;
    191   }
    192   if (GNUNET_OK !=
    193       GNUNET_JSON_parse (transaction,
    194                          withdraw_spec,
    195                          NULL, NULL))
    196   {
    197     GNUNET_break_op (0);
    198     return GNUNET_SYSERR;
    199   }
    200   rh->details.in_details.sender_url.full_payto
    201     = GNUNET_strdup (wire_uri.full_payto);
    202   rh->details.in_details.wire_reference = wire_reference;
    203   rh->details.in_details.timestamp = timestamp;
    204   return GNUNET_OK;
    205 }
    206 
    207 
    208 /**
    209  * Parse "withdraw" reserve history entry.
    210  *
    211  * @param[in,out] rh entry to parse
    212  * @param uc our context
    213  * @param transaction the transaction to parse
    214  * @return #GNUNET_OK on success
    215  */
    216 static enum GNUNET_GenericReturnValue
    217 parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    218                 struct HistoryParseContext *uc,
    219                 const json_t *transaction)
    220 {
    221   uint16_t num_coins;
    222   struct TALER_Amount withdraw_fee;
    223   struct TALER_Amount withdraw_amount;
    224   struct TALER_Amount amount_without_fee;
    225   uint8_t max_age = 0;
    226   uint8_t noreveal_index = 0;
    227   struct TALER_HashBlindedPlanchetsP planchets_h;
    228   struct TALER_HashBlindedPlanchetsP selected_h;
    229   struct TALER_ReserveSignatureP reserve_sig;
    230   struct TALER_BlindingMasterSeedP blinding_seed;
    231   struct TALER_DenominationHashP *denom_pub_hashes;
    232   size_t num_denom_pub_hashes;
    233   bool no_max_age;
    234   bool no_noreveal_index;
    235   bool no_blinding_seed;
    236   bool no_selected_h;
    237   struct GNUNET_JSON_Specification withdraw_spec[] = {
    238     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    239                                  &reserve_sig),
    240     GNUNET_JSON_spec_uint16 ("num_coins",
    241                              &num_coins),
    242     GNUNET_JSON_spec_fixed_auto ("planchets_h",
    243                                  &planchets_h),
    244     TALER_JSON_spec_amount_any ("amount",
    245                                 &withdraw_amount),
    246     TALER_JSON_spec_amount_any ("withdraw_fee",
    247                                 &withdraw_fee),
    248     TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes",
    249                                           &num_denom_pub_hashes,
    250                                           &denom_pub_hashes),
    251     GNUNET_JSON_spec_mark_optional (
    252       GNUNET_JSON_spec_fixed_auto ("selected_h",
    253                                    &selected_h),
    254       &no_selected_h),
    255     GNUNET_JSON_spec_mark_optional (
    256       GNUNET_JSON_spec_uint8 ("max_age",
    257                               &max_age),
    258       &no_max_age),
    259     GNUNET_JSON_spec_mark_optional (
    260       GNUNET_JSON_spec_fixed_auto ("blinding_seed",
    261                                    &blinding_seed),
    262       &no_blinding_seed),
    263     GNUNET_JSON_spec_mark_optional (
    264       GNUNET_JSON_spec_uint8 ("noreveal_index",
    265                               &noreveal_index),
    266       &no_noreveal_index),
    267     GNUNET_JSON_spec_end ()
    268   };
    269 
    270   rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
    271   if (GNUNET_OK !=
    272       GNUNET_JSON_parse (transaction,
    273                          withdraw_spec,
    274                          NULL, NULL))
    275   {
    276     GNUNET_break_op (0);
    277     return GNUNET_SYSERR;
    278   }
    279 
    280   if ((no_max_age != no_noreveal_index) ||
    281       (no_max_age != no_selected_h))
    282   {
    283     GNUNET_break_op (0);
    284     GNUNET_JSON_parse_free (withdraw_spec);
    285     return GNUNET_SYSERR;
    286   }
    287   rh->details.withdraw.age_restricted = ! no_max_age;
    288 
    289   if (num_coins != num_denom_pub_hashes)
    290   {
    291     GNUNET_break_op (0);
    292     GNUNET_JSON_parse_free (withdraw_spec);
    293     return GNUNET_SYSERR;
    294   }
    295 
    296   /* Check that the signature is a valid withdraw request */
    297   if (0>TALER_amount_subtract (
    298         &amount_without_fee,
    299         &withdraw_amount,
    300         &withdraw_fee))
    301   {
    302     GNUNET_break_op (0);
    303     GNUNET_JSON_parse_free (withdraw_spec);
    304     return GNUNET_SYSERR;
    305   }
    306 
    307   if (GNUNET_OK !=
    308       TALER_wallet_withdraw_verify (
    309         &amount_without_fee,
    310         &withdraw_fee,
    311         &planchets_h,
    312         no_blinding_seed ? NULL : &blinding_seed,
    313         no_max_age ? NULL : &uc->keys->age_mask,
    314         no_max_age ? 0 : max_age,
    315         uc->reserve_pub,
    316         &reserve_sig))
    317   {
    318     GNUNET_break_op (0);
    319     GNUNET_JSON_parse_free (withdraw_spec);
    320     return GNUNET_SYSERR;
    321   }
    322 
    323   rh->details.withdraw.num_coins = num_coins;
    324   rh->details.withdraw.age_restricted = ! no_max_age;
    325   rh->details.withdraw.max_age = max_age;
    326   rh->details.withdraw.planchets_h = planchets_h;
    327   rh->details.withdraw.selected_h = selected_h;
    328   rh->details.withdraw.noreveal_index = noreveal_index;
    329   rh->details.withdraw.no_blinding_seed = no_blinding_seed;
    330   if (! no_blinding_seed)
    331     rh->details.withdraw.blinding_seed = blinding_seed;
    332 
    333   /* check that withdraw fee matches expectations! */
    334   {
    335     const struct TALER_EXCHANGE_Keys *key_state;
    336     struct TALER_Amount fee_acc;
    337     struct TALER_Amount amount_acc;
    338 
    339     GNUNET_assert (GNUNET_OK ==
    340                    TALER_amount_set_zero (withdraw_amount.currency,
    341                                           &fee_acc));
    342     GNUNET_assert (GNUNET_OK ==
    343                    TALER_amount_set_zero (withdraw_amount.currency,
    344                                           &amount_acc));
    345 
    346     key_state = uc->keys;
    347 
    348     /* accumulate the withdraw fees */
    349     for (size_t i=0; i < num_coins; i++)
    350     {
    351       const struct TALER_EXCHANGE_DenomPublicKey *dki;
    352 
    353       dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
    354                                                          &denom_pub_hashes[i]);
    355       if (NULL == dki)
    356       {
    357         GNUNET_break_op (0);
    358         GNUNET_JSON_parse_free (withdraw_spec);
    359         return GNUNET_SYSERR;
    360       }
    361       GNUNET_assert (0 <=
    362                      TALER_amount_add (&fee_acc,
    363                                        &fee_acc,
    364                                        &dki->fees.withdraw));
    365       GNUNET_assert (0 <=
    366                      TALER_amount_add (&amount_acc,
    367                                        &amount_acc,
    368                                        &dki->value));
    369     }
    370 
    371     if ( (GNUNET_YES !=
    372           TALER_amount_cmp_currency (&fee_acc,
    373                                      &withdraw_fee)) ||
    374          (0 !=
    375           TALER_amount_cmp (&amount_acc,
    376                             &amount_without_fee)) )
    377     {
    378       GNUNET_break_op (0);
    379       GNUNET_JSON_parse_free (withdraw_spec);
    380       return GNUNET_SYSERR;
    381     }
    382     rh->details.withdraw.fee = withdraw_fee;
    383   }
    384 
    385   #pragma message "is out_authorization_sig still needed? Not set anywhere"
    386   rh->details.withdraw.out_authorization_sig
    387     = json_object_get (transaction,
    388                        "signature");
    389   /* Check check that the same withdraw transaction
    390        isn't listed twice by the exchange. We use the
    391        "uuid" array to remember the hashes of all
    392        signatures, and compare the hashes to find
    393        duplicates. */
    394   GNUNET_CRYPTO_hash (&reserve_sig,
    395                       sizeof (reserve_sig),
    396                       &uc->uuids[uc->uuid_off]);
    397   for (unsigned int i = 0; i<uc->uuid_off; i++)
    398   {
    399     if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
    400                             &uc->uuids[i]))
    401     {
    402       GNUNET_break_op (0);
    403       GNUNET_JSON_parse_free (withdraw_spec);
    404       return GNUNET_SYSERR;
    405     }
    406   }
    407   uc->uuid_off++;
    408 
    409   if (0 >
    410       TALER_amount_add (uc->total_out,
    411                         uc->total_out,
    412                         &rh->amount))
    413   {
    414     /* overflow in history already!? inconceivable! Bad exchange! */
    415     GNUNET_break_op (0);
    416     GNUNET_JSON_parse_free (withdraw_spec);
    417     return GNUNET_SYSERR;
    418   }
    419   GNUNET_JSON_parse_free (withdraw_spec);
    420   return GNUNET_OK;
    421 }
    422 
    423 
    424 /**
    425  * Parse "recoup" reserve history entry.
    426  *
    427  * @param[in,out] rh entry to parse
    428  * @param uc our context
    429  * @param transaction the transaction to parse
    430  * @return #GNUNET_OK on success
    431  */
    432 static enum GNUNET_GenericReturnValue
    433 parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    434               struct HistoryParseContext *uc,
    435               const json_t *transaction)
    436 {
    437   const struct TALER_EXCHANGE_Keys *key_state;
    438   struct GNUNET_JSON_Specification recoup_spec[] = {
    439     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    440                                  &rh->details.recoup_details.coin_pub),
    441     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    442                                  &rh->details.recoup_details.exchange_sig),
    443     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    444                                  &rh->details.recoup_details.exchange_pub),
    445     GNUNET_JSON_spec_timestamp ("timestamp",
    446                                 &rh->details.recoup_details.timestamp),
    447     GNUNET_JSON_spec_end ()
    448   };
    449 
    450   rh->type = TALER_EXCHANGE_RTT_RECOUP;
    451   if (GNUNET_OK !=
    452       GNUNET_JSON_parse (transaction,
    453                          recoup_spec,
    454                          NULL, NULL))
    455   {
    456     GNUNET_break_op (0);
    457     return GNUNET_SYSERR;
    458   }
    459   key_state = uc->keys;
    460   if (GNUNET_OK !=
    461       TALER_EXCHANGE_test_signing_key (key_state,
    462                                        &rh->details.
    463                                        recoup_details.exchange_pub))
    464   {
    465     GNUNET_break_op (0);
    466     return GNUNET_SYSERR;
    467   }
    468   if (GNUNET_OK !=
    469       TALER_exchange_online_confirm_recoup_verify (
    470         rh->details.recoup_details.timestamp,
    471         &rh->amount,
    472         &rh->details.recoup_details.coin_pub,
    473         uc->reserve_pub,
    474         &rh->details.recoup_details.exchange_pub,
    475         &rh->details.recoup_details.exchange_sig))
    476   {
    477     GNUNET_break_op (0);
    478     return GNUNET_SYSERR;
    479   }
    480   if (0 >
    481       TALER_amount_add (uc->total_in,
    482                         uc->total_in,
    483                         &rh->amount))
    484   {
    485     /* overflow in history already!? inconceivable! Bad exchange! */
    486     GNUNET_break_op (0);
    487     return GNUNET_SYSERR;
    488   }
    489   return GNUNET_OK;
    490 }
    491 
    492 
    493 /**
    494  * Parse "closing" reserve history entry.
    495  *
    496  * @param[in,out] rh entry to parse
    497  * @param uc our context
    498  * @param transaction the transaction to parse
    499  * @return #GNUNET_OK on success
    500  */
    501 static enum GNUNET_GenericReturnValue
    502 parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    503                struct HistoryParseContext *uc,
    504                const json_t *transaction)
    505 {
    506   const struct TALER_EXCHANGE_Keys *key_state;
    507   struct TALER_FullPayto receiver_uri;
    508   struct GNUNET_JSON_Specification closing_spec[] = {
    509     TALER_JSON_spec_full_payto_uri (
    510       "receiver_account_details",
    511       &receiver_uri),
    512     GNUNET_JSON_spec_fixed_auto ("wtid",
    513                                  &rh->details.close_details.wtid),
    514     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    515                                  &rh->details.close_details.exchange_sig),
    516     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    517                                  &rh->details.close_details.exchange_pub),
    518     TALER_JSON_spec_amount_any ("closing_fee",
    519                                 &rh->details.close_details.fee),
    520     GNUNET_JSON_spec_timestamp ("timestamp",
    521                                 &rh->details.close_details.timestamp),
    522     GNUNET_JSON_spec_end ()
    523   };
    524 
    525   rh->type = TALER_EXCHANGE_RTT_CLOSING;
    526   if (GNUNET_OK !=
    527       GNUNET_JSON_parse (transaction,
    528                          closing_spec,
    529                          NULL, NULL))
    530   {
    531     GNUNET_break_op (0);
    532     return GNUNET_SYSERR;
    533   }
    534   key_state = uc->keys;
    535   if (GNUNET_OK !=
    536       TALER_EXCHANGE_test_signing_key (
    537         key_state,
    538         &rh->details.close_details.exchange_pub))
    539   {
    540     GNUNET_break_op (0);
    541     return GNUNET_SYSERR;
    542   }
    543   if (GNUNET_OK !=
    544       TALER_exchange_online_reserve_closed_verify (
    545         rh->details.close_details.timestamp,
    546         &rh->amount,
    547         &rh->details.close_details.fee,
    548         receiver_uri,
    549         &rh->details.close_details.wtid,
    550         uc->reserve_pub,
    551         &rh->details.close_details.exchange_pub,
    552         &rh->details.close_details.exchange_sig))
    553   {
    554     GNUNET_break_op (0);
    555     return GNUNET_SYSERR;
    556   }
    557   if (0 >
    558       TALER_amount_add (uc->total_out,
    559                         uc->total_out,
    560                         &rh->amount))
    561   {
    562     /* overflow in history already!? inconceivable! Bad exchange! */
    563     GNUNET_break_op (0);
    564     return GNUNET_SYSERR;
    565   }
    566   rh->details.close_details.receiver_account_details.full_payto
    567     = GNUNET_strdup (receiver_uri.full_payto);
    568   return GNUNET_OK;
    569 }
    570 
    571 
    572 /**
    573  * Parse "merge" reserve history entry.
    574  *
    575  * @param[in,out] rh entry to parse
    576  * @param uc our context
    577  * @param transaction the transaction to parse
    578  * @return #GNUNET_OK on success
    579  */
    580 static enum GNUNET_GenericReturnValue
    581 parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    582              struct HistoryParseContext *uc,
    583              const json_t *transaction)
    584 {
    585   uint32_t flags32;
    586   struct GNUNET_JSON_Specification merge_spec[] = {
    587     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    588                                  &rh->details.merge_details.h_contract_terms),
    589     GNUNET_JSON_spec_fixed_auto ("merge_pub",
    590                                  &rh->details.merge_details.merge_pub),
    591     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    592                                  &rh->details.merge_details.purse_pub),
    593     GNUNET_JSON_spec_uint32 ("min_age",
    594                              &rh->details.merge_details.min_age),
    595     GNUNET_JSON_spec_uint32 ("flags",
    596                              &flags32),
    597     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    598                                  &rh->details.merge_details.reserve_sig),
    599     TALER_JSON_spec_amount_any ("purse_fee",
    600                                 &rh->details.merge_details.purse_fee),
    601     GNUNET_JSON_spec_timestamp ("merge_timestamp",
    602                                 &rh->details.merge_details.merge_timestamp),
    603     GNUNET_JSON_spec_timestamp ("purse_expiration",
    604                                 &rh->details.merge_details.purse_expiration),
    605     GNUNET_JSON_spec_bool ("merged",
    606                            &rh->details.merge_details.merged),
    607     GNUNET_JSON_spec_end ()
    608   };
    609 
    610   rh->type = TALER_EXCHANGE_RTT_MERGE;
    611   if (GNUNET_OK !=
    612       GNUNET_JSON_parse (transaction,
    613                          merge_spec,
    614                          NULL, NULL))
    615   {
    616     GNUNET_break_op (0);
    617     return GNUNET_SYSERR;
    618   }
    619   rh->details.merge_details.flags =
    620     (enum TALER_WalletAccountMergeFlags) flags32;
    621   if (GNUNET_OK !=
    622       TALER_wallet_account_merge_verify (
    623         rh->details.merge_details.merge_timestamp,
    624         &rh->details.merge_details.purse_pub,
    625         rh->details.merge_details.purse_expiration,
    626         &rh->details.merge_details.h_contract_terms,
    627         &rh->amount,
    628         &rh->details.merge_details.purse_fee,
    629         rh->details.merge_details.min_age,
    630         rh->details.merge_details.flags,
    631         uc->reserve_pub,
    632         &rh->details.merge_details.reserve_sig))
    633   {
    634     GNUNET_break_op (0);
    635     return GNUNET_SYSERR;
    636   }
    637   if (rh->details.merge_details.merged)
    638   {
    639     if (0 >
    640         TALER_amount_add (uc->total_in,
    641                           uc->total_in,
    642                           &rh->amount))
    643     {
    644       /* overflow in history already!? inconceivable! Bad exchange! */
    645       GNUNET_break_op (0);
    646       return GNUNET_SYSERR;
    647     }
    648   }
    649   else
    650   {
    651     if (0 >
    652         TALER_amount_add (uc->total_out,
    653                           uc->total_out,
    654                           &rh->details.merge_details.purse_fee))
    655     {
    656       /* overflow in history already!? inconceivable! Bad exchange! */
    657       GNUNET_break_op (0);
    658       return GNUNET_SYSERR;
    659     }
    660   }
    661   return GNUNET_OK;
    662 }
    663 
    664 
    665 /**
    666  * Parse "open" reserve open entry.
    667  *
    668  * @param[in,out] rh entry to parse
    669  * @param uc our context
    670  * @param transaction the transaction to parse
    671  * @return #GNUNET_OK on success
    672  */
    673 static enum GNUNET_GenericReturnValue
    674 parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    675             struct HistoryParseContext *uc,
    676             const json_t *transaction)
    677 {
    678   struct GNUNET_JSON_Specification open_spec[] = {
    679     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    680                                  &rh->details.open_request.reserve_sig),
    681     TALER_JSON_spec_amount_any ("open_fee",
    682                                 &rh->details.open_request.reserve_payment),
    683     GNUNET_JSON_spec_uint32 ("requested_min_purses",
    684                              &rh->details.open_request.purse_limit),
    685     GNUNET_JSON_spec_timestamp ("request_timestamp",
    686                                 &rh->details.open_request.request_timestamp),
    687     GNUNET_JSON_spec_timestamp ("requested_expiration",
    688                                 &rh->details.open_request.reserve_expiration),
    689     GNUNET_JSON_spec_end ()
    690   };
    691 
    692   rh->type = TALER_EXCHANGE_RTT_OPEN;
    693   if (GNUNET_OK !=
    694       GNUNET_JSON_parse (transaction,
    695                          open_spec,
    696                          NULL, NULL))
    697   {
    698     GNUNET_break_op (0);
    699     return GNUNET_SYSERR;
    700   }
    701   if (GNUNET_OK !=
    702       TALER_wallet_reserve_open_verify (
    703         &rh->amount,
    704         rh->details.open_request.request_timestamp,
    705         rh->details.open_request.reserve_expiration,
    706         rh->details.open_request.purse_limit,
    707         uc->reserve_pub,
    708         &rh->details.open_request.reserve_sig))
    709   {
    710     GNUNET_break_op (0);
    711     return GNUNET_SYSERR;
    712   }
    713   if (0 >
    714       TALER_amount_add (uc->total_out,
    715                         uc->total_out,
    716                         &rh->amount))
    717   {
    718     /* overflow in history already!? inconceivable! Bad exchange! */
    719     GNUNET_break_op (0);
    720     return GNUNET_SYSERR;
    721   }
    722   return GNUNET_OK;
    723 }
    724 
    725 
    726 /**
    727  * Parse "close" reserve close entry.
    728  *
    729  * @param[in,out] rh entry to parse
    730  * @param uc our context
    731  * @param transaction the transaction to parse
    732  * @return #GNUNET_OK on success
    733  */
    734 static enum GNUNET_GenericReturnValue
    735 parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
    736              struct HistoryParseContext *uc,
    737              const json_t *transaction)
    738 {
    739   struct GNUNET_JSON_Specification close_spec[] = {
    740     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    741                                  &rh->details.close_request.reserve_sig),
    742     GNUNET_JSON_spec_mark_optional (
    743       GNUNET_JSON_spec_fixed_auto ("h_payto",
    744                                    &rh->details.close_request.
    745                                    target_account_h_payto),
    746       NULL),
    747     GNUNET_JSON_spec_timestamp ("request_timestamp",
    748                                 &rh->details.close_request.request_timestamp),
    749     GNUNET_JSON_spec_end ()
    750   };
    751 
    752   rh->type = TALER_EXCHANGE_RTT_CLOSE;
    753   if (GNUNET_OK !=
    754       GNUNET_JSON_parse (transaction,
    755                          close_spec,
    756                          NULL, NULL))
    757   {
    758     GNUNET_break_op (0);
    759     return GNUNET_SYSERR;
    760   }
    761   /* force amount to invalid */
    762   memset (&rh->amount,
    763           0,
    764           sizeof (rh->amount));
    765   if (GNUNET_OK !=
    766       TALER_wallet_reserve_close_verify (
    767         rh->details.close_request.request_timestamp,
    768         &rh->details.close_request.target_account_h_payto,
    769         uc->reserve_pub,
    770         &rh->details.close_request.reserve_sig))
    771   {
    772     GNUNET_break_op (0);
    773     return GNUNET_SYSERR;
    774   }
    775   return GNUNET_OK;
    776 }
    777 
    778 
    779 static void
    780 free_reserve_history (
    781   unsigned int len,
    782   struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
    783 {
    784   for (unsigned int i = 0; i<len; i++)
    785   {
    786     struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i];
    787 
    788     switch (rhi->type)
    789     {
    790     case TALER_EXCHANGE_RTT_CREDIT:
    791       GNUNET_free (rhi->details.in_details.sender_url.full_payto);
    792       break;
    793     case TALER_EXCHANGE_RTT_WITHDRAWAL:
    794       break;
    795     case TALER_EXCHANGE_RTT_RECOUP:
    796       break;
    797     case TALER_EXCHANGE_RTT_CLOSING:
    798       GNUNET_free (
    799         rhi->details.close_details.receiver_account_details.full_payto);
    800       break;
    801     case TALER_EXCHANGE_RTT_MERGE:
    802       break;
    803     case TALER_EXCHANGE_RTT_OPEN:
    804       break;
    805     case TALER_EXCHANGE_RTT_CLOSE:
    806       break;
    807     }
    808   }
    809   GNUNET_free (rhistory);
    810 }
    811 
    812 
    813 /**
    814  * Parse history given in JSON format and return it in binary format.
    815  *
    816  * @param keys exchange keys
    817  * @param history JSON array with the history
    818  * @param reserve_pub public key of the reserve to inspect
    819  * @param currency currency we expect the balance to be in
    820  * @param[out] total_in set to value of credits to reserve
    821  * @param[out] total_out set to value of debits from reserve
    822  * @param history_length number of entries in @a history
    823  * @param[out] rhistory array of length @a history_length, set to the
    824  *             parsed history entries
    825  * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
    826  *         were set,
    827  *         #GNUNET_SYSERR if there was a protocol violation in @a history
    828  */
    829 static enum GNUNET_GenericReturnValue
    830 parse_reserve_history (
    831   const struct TALER_EXCHANGE_Keys *keys,
    832   const json_t *history,
    833   const struct TALER_ReservePublicKeyP *reserve_pub,
    834   const char *currency,
    835   struct TALER_Amount *total_in,
    836   struct TALER_Amount *total_out,
    837   unsigned int history_length,
    838   struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
    839 {
    840   const struct
    841   {
    842     const char *type;
    843     ParseHelper helper;
    844   } map[] = {
    845     { "CREDIT", &parse_credit },
    846     { "WITHDRAW", &parse_withdraw },
    847     { "RECOUP", &parse_recoup },
    848     { "MERGE", &parse_merge },
    849     { "CLOSING", &parse_closing },
    850     { "OPEN", &parse_open },
    851     { "CLOSE", &parse_close },
    852     { NULL, NULL }
    853   };
    854   struct GNUNET_HashCode *uuid
    855     = GNUNET_new_array (history_length,
    856                         struct GNUNET_HashCode);
    857   struct HistoryParseContext uc = {
    858     .keys = keys,
    859     .reserve_pub = reserve_pub,
    860     .uuids = uuid,
    861     .total_in = total_in,
    862     .total_out = total_out
    863   };
    864 
    865   GNUNET_assert (GNUNET_OK ==
    866                  TALER_amount_set_zero (currency,
    867                                         total_in));
    868   GNUNET_assert (GNUNET_OK ==
    869                  TALER_amount_set_zero (currency,
    870                                         total_out));
    871   for (unsigned int off = 0; off<history_length; off++)
    872   {
    873     struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
    874     json_t *transaction;
    875     const char *type;
    876     struct GNUNET_JSON_Specification hist_spec[] = {
    877       GNUNET_JSON_spec_string ("type",
    878                                &type),
    879       TALER_JSON_spec_amount_any ("amount",
    880                                   &rh->amount),
    881       GNUNET_JSON_spec_uint64 ("history_offset",
    882                                &rh->history_offset),
    883       /* 'wire' and 'signature' are optional depending on 'type'! */
    884       GNUNET_JSON_spec_end ()
    885     };
    886     bool found = false;
    887 
    888     transaction = json_array_get (history,
    889                                   off);
    890     if (GNUNET_OK !=
    891         GNUNET_JSON_parse (transaction,
    892                            hist_spec,
    893                            NULL, NULL))
    894     {
    895       GNUNET_break_op (0);
    896       json_dumpf (transaction,
    897                   stderr,
    898                   JSON_INDENT (2));
    899       GNUNET_free (uuid);
    900       return GNUNET_SYSERR;
    901     }
    902     if (GNUNET_YES !=
    903         TALER_amount_cmp_currency (&rh->amount,
    904                                    total_in))
    905     {
    906       GNUNET_break_op (0);
    907       GNUNET_free (uuid);
    908       return GNUNET_SYSERR;
    909     }
    910     for (unsigned int i = 0; NULL != map[i].type; i++)
    911     {
    912       if (0 == strcasecmp (map[i].type,
    913                            type))
    914       {
    915         found = true;
    916         if (GNUNET_OK !=
    917             map[i].helper (rh,
    918                            &uc,
    919                            transaction))
    920         {
    921           GNUNET_break_op (0);
    922           GNUNET_free (uuid);
    923           return GNUNET_SYSERR;
    924         }
    925         break;
    926       }
    927     }
    928     if (! found)
    929     {
    930       /* unexpected 'type', protocol incompatibility, complain! */
    931       GNUNET_break_op (0);
    932       GNUNET_free (uuid);
    933       return GNUNET_SYSERR;
    934     }
    935   }
    936   GNUNET_free (uuid);
    937   return GNUNET_OK;
    938 }
    939 
    940 
    941 /**
    942  * Handle HTTP header received by curl.
    943  *
    944  * @param buffer one line of HTTP header data
    945  * @param size size of an item
    946  * @param nitems number of items passed
    947  * @param userdata our `struct TALER_EXCHANGE_GetReservesHistoryHandle *`
    948  * @return `size * nitems`
    949  */
    950 static size_t
    951 handle_header (char *buffer,
    952                size_t size,
    953                size_t nitems,
    954                void *userdata)
    955 {
    956   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = userdata;
    957   size_t total = size * nitems;
    958   char *ndup;
    959   const char *hdr_type;
    960   char *hdr_val;
    961   char *sp;
    962 
    963   ndup = GNUNET_strndup (buffer,
    964                          total);
    965   hdr_type = strtok_r (ndup,
    966                        ":",
    967                        &sp);
    968   if (NULL == hdr_type)
    969   {
    970     GNUNET_free (ndup);
    971     return total;
    972   }
    973   hdr_val = strtok_r (NULL,
    974                       "\n\r",
    975                       &sp);
    976   if (NULL == hdr_val)
    977   {
    978     GNUNET_free (ndup);
    979     return total;
    980   }
    981   if (' ' == *hdr_val)
    982     hdr_val++;
    983   if (0 == strcasecmp (hdr_type,
    984                        MHD_HTTP_HEADER_ETAG))
    985   {
    986     unsigned long long tval;
    987     char dummy;
    988 
    989     if (1 !=
    990         sscanf (hdr_val,
    991                 "\"%llu\"%c",
    992                 &tval,
    993                 &dummy))
    994     {
    995       GNUNET_break_op (0);
    996       GNUNET_free (ndup);
    997       return 0;
    998     }
    999     grhh->etag = (uint64_t) tval;
   1000   }
   1001   GNUNET_free (ndup);
   1002   return total;
   1003 }
   1004 
   1005 
   1006 /**
   1007  * We received an #MHD_HTTP_OK status code. Handle the JSON response.
   1008  *
   1009  * @param grhh handle of the request
   1010  * @param j JSON response
   1011  * @return #GNUNET_OK on success
   1012  */
   1013 static enum GNUNET_GenericReturnValue
   1014 handle_reserves_history_ok (
   1015   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1016   const json_t *j)
   1017 {
   1018   const json_t *history;
   1019   unsigned int len;
   1020   struct TALER_EXCHANGE_GetReservesHistoryResponse rs = {
   1021     .hr.reply = j,
   1022     .hr.http_status = MHD_HTTP_OK,
   1023     .details.ok.etag = grhh->etag
   1024   };
   1025   struct GNUNET_JSON_Specification spec[] = {
   1026     TALER_JSON_spec_amount_any ("balance",
   1027                                 &rs.details.ok.balance),
   1028     GNUNET_JSON_spec_array_const ("history",
   1029                                   &history),
   1030     GNUNET_JSON_spec_end ()
   1031   };
   1032 
   1033   if (GNUNET_OK !=
   1034       GNUNET_JSON_parse (j,
   1035                          spec,
   1036                          NULL,
   1037                          NULL))
   1038   {
   1039     GNUNET_break_op (0);
   1040     return GNUNET_SYSERR;
   1041   }
   1042   len = json_array_size (history);
   1043   {
   1044     struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
   1045 
   1046     rhistory = GNUNET_new_array (len,
   1047                                  struct TALER_EXCHANGE_ReserveHistoryEntry);
   1048     if (GNUNET_OK !=
   1049         parse_reserve_history (grhh->keys,
   1050                                history,
   1051                                &grhh->reserve_pub,
   1052                                rs.details.ok.balance.currency,
   1053                                &rs.details.ok.total_in,
   1054                                &rs.details.ok.total_out,
   1055                                len,
   1056                                rhistory))
   1057     {
   1058       GNUNET_break_op (0);
   1059       free_reserve_history (len,
   1060                             rhistory);
   1061       GNUNET_JSON_parse_free (spec);
   1062       return GNUNET_SYSERR;
   1063     }
   1064     if (NULL != grhh->cb)
   1065     {
   1066       rs.details.ok.history = rhistory;
   1067       rs.details.ok.history_len = len;
   1068       grhh->cb (grhh->cb_cls,
   1069                 &rs);
   1070       grhh->cb = NULL;
   1071     }
   1072     free_reserve_history (len,
   1073                           rhistory);
   1074   }
   1075   return GNUNET_OK;
   1076 }
   1077 
   1078 
   1079 /**
   1080  * Function called when we're done processing the
   1081  * HTTP GET /reserves/$RESERVE_PUB/history request.
   1082  *
   1083  * @param cls the `struct TALER_EXCHANGE_GetReservesHistoryHandle`
   1084  * @param response_code HTTP response code, 0 on error
   1085  * @param response parsed JSON result, NULL on error
   1086  */
   1087 static void
   1088 handle_reserves_history_finished (void *cls,
   1089                                   long response_code,
   1090                                   const void *response)
   1091 {
   1092   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh = cls;
   1093   const json_t *j = response;
   1094   struct TALER_EXCHANGE_GetReservesHistoryResponse rs = {
   1095     .hr.reply = j,
   1096     .hr.http_status = (unsigned int) response_code
   1097   };
   1098 
   1099   grhh->job = NULL;
   1100   switch (response_code)
   1101   {
   1102   case 0:
   1103     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
   1104     break;
   1105   case MHD_HTTP_OK:
   1106     if (GNUNET_OK !=
   1107         handle_reserves_history_ok (grhh,
   1108                                     j))
   1109     {
   1110       rs.hr.http_status = 0;
   1111       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
   1112     }
   1113     break;
   1114   case MHD_HTTP_BAD_REQUEST:
   1115     /* This should never happen, either us or the exchange is buggy
   1116        (or API version conflict); just pass JSON reply to the application */
   1117     GNUNET_break (0);
   1118     rs.hr.ec = TALER_JSON_get_error_code (j);
   1119     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1120     break;
   1121   case MHD_HTTP_FORBIDDEN:
   1122     /* This should never happen, either us or the exchange is buggy
   1123        (or API version conflict); just pass JSON reply to the application */
   1124     GNUNET_break (0);
   1125     rs.hr.ec = TALER_JSON_get_error_code (j);
   1126     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1127     break;
   1128   case MHD_HTTP_NOT_FOUND:
   1129     /* Nothing really to verify, this should never
   1130        happen, we should pass the JSON reply to the application */
   1131     rs.hr.ec = TALER_JSON_get_error_code (j);
   1132     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1133     break;
   1134   case MHD_HTTP_INTERNAL_SERVER_ERROR:
   1135     /* Server had an internal issue; we should retry, but this API
   1136        leaves this to the application */
   1137     rs.hr.ec = TALER_JSON_get_error_code (j);
   1138     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1139     break;
   1140   default:
   1141     /* unexpected response code */
   1142     GNUNET_break_op (0);
   1143     rs.hr.ec = TALER_JSON_get_error_code (j);
   1144     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1145     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1146                 "Unexpected response code %u/%d for reserves history\n",
   1147                 (unsigned int) response_code,
   1148                 (int) rs.hr.ec);
   1149     break;
   1150   }
   1151   if (NULL != grhh->cb)
   1152   {
   1153     grhh->cb (grhh->cb_cls,
   1154               &rs);
   1155     grhh->cb = NULL;
   1156   }
   1157   TALER_EXCHANGE_get_reserves_history_cancel (grhh);
   1158 }
   1159 
   1160 
   1161 struct TALER_EXCHANGE_GetReservesHistoryHandle *
   1162 TALER_EXCHANGE_get_reserves_history_create (
   1163   struct GNUNET_CURL_Context *ctx,
   1164   const char *url,
   1165   struct TALER_EXCHANGE_Keys *keys,
   1166   const struct TALER_ReservePrivateKeyP *reserve_priv)
   1167 {
   1168   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh;
   1169 
   1170   grhh = GNUNET_new (struct TALER_EXCHANGE_GetReservesHistoryHandle);
   1171   grhh->ctx = ctx;
   1172   grhh->base_url = GNUNET_strdup (url);
   1173   grhh->keys = TALER_EXCHANGE_keys_incref (keys);
   1174   grhh->reserve_priv = *reserve_priv;
   1175   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
   1176                                       &grhh->reserve_pub.eddsa_pub);
   1177   return grhh;
   1178 }
   1179 
   1180 
   1181 enum GNUNET_GenericReturnValue
   1182 TALER_EXCHANGE_get_reserves_history_set_options_ (
   1183   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1184   unsigned int num_options,
   1185   const struct TALER_EXCHANGE_GetReservesHistoryOptionValue *options)
   1186 {
   1187   for (unsigned int i = 0; i < num_options; i++)
   1188   {
   1189     switch (options[i].option)
   1190     {
   1191     case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_END:
   1192       return GNUNET_OK;
   1193     case TALER_EXCHANGE_GET_RESERVES_HISTORY_OPTION_START_OFF:
   1194       grhh->options.start_off = options[i].details.start_off;
   1195       break;
   1196     default:
   1197       GNUNET_break (0);
   1198       return GNUNET_SYSERR;
   1199     }
   1200   }
   1201   return GNUNET_OK;
   1202 }
   1203 
   1204 
   1205 enum TALER_ErrorCode
   1206 TALER_EXCHANGE_get_reserves_history_start (
   1207   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh,
   1208   TALER_EXCHANGE_GetReservesHistoryCallback cb,
   1209   TALER_EXCHANGE_GET_RESERVES_HISTORY_RESULT_CLOSURE *cb_cls)
   1210 {
   1211   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
   1212   struct curl_slist *job_headers;
   1213   CURL *eh;
   1214 
   1215   if (NULL != grhh->job)
   1216   {
   1217     GNUNET_break (0);
   1218     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1219   }
   1220   grhh->cb = cb;
   1221   grhh->cb_cls = cb_cls;
   1222   {
   1223     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
   1224     char *end;
   1225     char start_off_str[32];
   1226 
   1227     end = GNUNET_STRINGS_data_to_string (
   1228       &grhh->reserve_pub,
   1229       sizeof (grhh->reserve_pub),
   1230       pub_str,
   1231       sizeof (pub_str));
   1232     *end = '\0';
   1233     GNUNET_snprintf (arg_str,
   1234                      sizeof (arg_str),
   1235                      "reserves/%s/history",
   1236                      pub_str);
   1237     GNUNET_snprintf (start_off_str,
   1238                      sizeof (start_off_str),
   1239                      "%llu",
   1240                      (unsigned long long) grhh->options.start_off);
   1241     grhh->url = TALER_url_join (grhh->base_url,
   1242                                 arg_str,
   1243                                 "start",
   1244                                 (0 != grhh->options.start_off)
   1245                                  ? start_off_str
   1246                                  : NULL,
   1247                                 NULL);
   1248   }
   1249   if (NULL == grhh->url)
   1250     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1251   eh = TALER_EXCHANGE_curl_easy_get_ (grhh->url);
   1252   if (NULL == eh)
   1253   {
   1254     GNUNET_free (grhh->url);
   1255     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
   1256   }
   1257   GNUNET_assert (CURLE_OK ==
   1258                  curl_easy_setopt (eh,
   1259                                    CURLOPT_HEADERFUNCTION,
   1260                                    &handle_header));
   1261   GNUNET_assert (CURLE_OK ==
   1262                  curl_easy_setopt (eh,
   1263                                    CURLOPT_HEADERDATA,
   1264                                    grhh));
   1265   {
   1266     struct TALER_ReserveSignatureP reserve_sig;
   1267     char *sig_hdr;
   1268     char *hdr;
   1269 
   1270     TALER_wallet_reserve_history_sign (grhh->options.start_off,
   1271                                        &grhh->reserve_priv,
   1272                                        &reserve_sig);
   1273     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
   1274       &reserve_sig,
   1275       sizeof (reserve_sig));
   1276     GNUNET_asprintf (&hdr,
   1277                      "%s: %s",
   1278                      TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
   1279                      sig_hdr);
   1280     GNUNET_free (sig_hdr);
   1281     job_headers = curl_slist_append (NULL,
   1282                                      hdr);
   1283     GNUNET_free (hdr);
   1284     if (NULL == job_headers)
   1285     {
   1286       GNUNET_break (0);
   1287       curl_easy_cleanup (eh);
   1288       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1289     }
   1290   }
   1291   grhh->job = GNUNET_CURL_job_add2 (grhh->ctx,
   1292                                     eh,
   1293                                     job_headers,
   1294                                     &handle_reserves_history_finished,
   1295                                     grhh);
   1296   curl_slist_free_all (job_headers);
   1297   if (NULL == grhh->job)
   1298     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
   1299   return TALER_EC_NONE;
   1300 }
   1301 
   1302 
   1303 void
   1304 TALER_EXCHANGE_get_reserves_history_cancel (
   1305   struct TALER_EXCHANGE_GetReservesHistoryHandle *grhh)
   1306 {
   1307   if (NULL != grhh->job)
   1308   {
   1309     GNUNET_CURL_job_cancel (grhh->job);
   1310     grhh->job = NULL;
   1311   }
   1312   GNUNET_free (grhh->url);
   1313   GNUNET_free (grhh->base_url);
   1314   TALER_EXCHANGE_keys_decref (grhh->keys);
   1315   GNUNET_free (grhh);
   1316 }
   1317 
   1318 
   1319 /* end of exchange_api_get-reserves-RESERVE_PUB-history.c */