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 (39860B)


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